@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/cli.mjs CHANGED
@@ -3618,6 +3618,178 @@ function buildElements(deps) {
3618
3618
  return app;
3619
3619
  }
3620
3620
 
3621
+ // src/mock/routes/images.ts
3622
+ var SLUG_KEYWORDS = {
3623
+ // ─── Akua's Bakery (default seed) ───
3624
+ sourdough: "sourdough,bread",
3625
+ wheat: "whole-wheat,bread",
3626
+ baguette: "baguette",
3627
+ focaccia: "focaccia,bread",
3628
+ croissant: "croissant",
3629
+ pac: "pain-au-chocolat,croissant",
3630
+ danish: "danish,pastry",
3631
+ cinnamon: "cinnamon-roll",
3632
+ "choc-cake": "chocolate-cake,slice",
3633
+ "red-velvet": "red-velvet-cake",
3634
+ carrot: "carrot-cake",
3635
+ cheesecake: "cheesecake",
3636
+ coffee: "coffee,cup",
3637
+ tea: "tea,cup",
3638
+ sobolo: "hibiscus-drink",
3639
+ oj: "orange-juice,glass",
3640
+ banana: "banana-bread",
3641
+ brownie: "brownie",
3642
+ macaron: "macaron,box",
3643
+ birthday: "birthday-cake",
3644
+ "breakfast-box": "breakfast",
3645
+ "pastry-box": "pastry,assortment",
3646
+ // ─── Mama's Kitchen (restaurant seed) ───
3647
+ kelewele: "fried-plantain,spicy",
3648
+ "spring-rolls": "spring-rolls",
3649
+ "soup-of-the-day": "soup,bowl",
3650
+ "jollof-rice": "jollof-rice",
3651
+ "banku-tilapia": "tilapia,grilled-fish",
3652
+ "fufu-light-soup": "fufu,soup",
3653
+ waakye: "rice,beans,african-food",
3654
+ "grilled-chicken": "grilled-chicken",
3655
+ burger: "burger",
3656
+ "yam-fries": "yam-fries,chips",
3657
+ "fried-plantain": "fried-plantain",
3658
+ salad: "garden-salad",
3659
+ "ice-cream": "ice-cream,scoop",
3660
+ "cake-slice": "chocolate-cake,slice",
3661
+ "fruit-bowl": "fruit-bowl,tropical",
3662
+ "soft-drink": "cola,glass",
3663
+ "fresh-juice": "fresh-juice",
3664
+ water: "water-bottle",
3665
+ feast: "family-meal,buffet",
3666
+ byo: "buffet,plate",
3667
+ // ─── Currents Electronics ───
3668
+ "android-flagship": "smartphone,android",
3669
+ "android-mid": "smartphone",
3670
+ "phone-budget": "phone",
3671
+ "laptop-pro": "laptop,macbook",
3672
+ "laptop-air": "laptop,thin",
3673
+ "laptop-budget": "laptop",
3674
+ "headphones-anc": "headphones",
3675
+ "earbuds-pro": "earbuds,wireless",
3676
+ "speaker-bt": "speaker,bluetooth",
3677
+ "phone-case": "phone-case",
3678
+ "screen-protector": "screen,protector",
3679
+ "fast-charger": "charger,usb",
3680
+ "laptop-sleeve": "laptop-sleeve,case",
3681
+ "wireless-mouse": "wireless-mouse",
3682
+ "mech-keyboard": "mechanical-keyboard",
3683
+ "gaming-controller": "game-controller",
3684
+ "gaming-headset": "gaming-headset",
3685
+ "console-handheld": "handheld-console,gaming",
3686
+ wfh: "desk-setup,laptop",
3687
+ "game-pack": "gaming,console",
3688
+ // ─── Reesa apparel ───
3689
+ "ankara-shirt": "ankara,african-fashion",
3690
+ "agbada-set": "agbada,nigerian-fashion",
3691
+ kaftan: "kaftan,african-dress",
3692
+ "leather-tote": "leather-tote,bag",
3693
+ sandals: "sandals,leather",
3694
+ "beaded-bracelet": "beaded-bracelet",
3695
+ owambe: "african-fashion",
3696
+ // ─── FreshMart ───
3697
+ "tomatoes-1kg": "tomatoes",
3698
+ "onions-1kg": "onions",
3699
+ "ginger-200g": "ginger,root",
3700
+ "bananas-bunch": "bananas",
3701
+ pineapple: "pineapple",
3702
+ "spinach-bunch": "spinach,leaves",
3703
+ "milk-1l": "milk,carton",
3704
+ "yogurt-500g": "yogurt",
3705
+ "eggs-12": "eggs,carton",
3706
+ "cheese-200g": "cheese",
3707
+ "chicken-1kg": "raw-chicken",
3708
+ "beef-mince-500g": "beef,mince",
3709
+ "tilapia-each": "fish,tilapia",
3710
+ "rice-5kg": "rice,bag",
3711
+ "garri-2kg": "rice,grain",
3712
+ "palm-oil-1l": "oil,bottle",
3713
+ "vegetable-oil-1l": "oil,bottle",
3714
+ "salt-1kg": "salt,seasoning",
3715
+ "sugar-1kg": "sugar,white",
3716
+ "tomato-paste": "tomato-paste,can",
3717
+ "soap-bar": "soap,bar",
3718
+ "detergent-1kg": "detergent,laundry",
3719
+ "tissue-pack": "tissue,paper",
3720
+ "trash-bags": "trash-bag",
3721
+ essentials: "groceries,box",
3722
+ // ─── Serene Spa ───
3723
+ "swedish-massage-60": "massage,relaxation",
3724
+ "swedish-massage-90": "massage,relaxation",
3725
+ "deep-tissue-60": "massage,deep-tissue",
3726
+ "couples-massage": "couples-massage",
3727
+ "hot-stone-90": "hot-stone,massage",
3728
+ "facial-classic": "facial,spa",
3729
+ "facial-anti-aging": "facial,skincare",
3730
+ "facial-acne": "facial,skincare",
3731
+ manicure: "manicure,nails",
3732
+ pedicure: "pedicure,feet",
3733
+ "gel-manicure": "manicure,gel-nails",
3734
+ haircut: "haircut,salon",
3735
+ "hair-treatment": "hair-treatment,salon",
3736
+ color: "hair-color,salon",
3737
+ "yoga-session": "yoga,studio",
3738
+ meditation: "meditation,zen",
3739
+ "spa-day": "spa,wellness",
3740
+ "couples-retreat": "spa,couple",
3741
+ "custom-experience": "spa,wellness"
3742
+ };
3743
+ var NO_CACHE = {
3744
+ "Cache-Control": "no-cache, no-store, must-revalidate"
3745
+ };
3746
+ function lockFromSlug(slug) {
3747
+ let hash = 0;
3748
+ for (let i = 0; i < slug.length; i++) hash = hash * 31 + slug.charCodeAt(i) >>> 0;
3749
+ return hash % 9999 + 1;
3750
+ }
3751
+ function svgFallback(slug) {
3752
+ const label = slug.replace(/[-_]+/g, " ").trim() || "product";
3753
+ const display = label.length > 20 ? label.slice(0, 20) + "\u2026" : label;
3754
+ let hash = 0;
3755
+ for (let i = 0; i < slug.length; i++) hash = hash * 31 + slug.charCodeAt(i) >>> 0;
3756
+ const hue = hash % 360;
3757
+ const safe = display.replace(/[<>&]/g, "");
3758
+ const svg = `<?xml version="1.0" encoding="UTF-8"?>
3759
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400">
3760
+ <defs>
3761
+ <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
3762
+ <stop offset="0%" stop-color="hsl(${hue}, 65%, 65%)"/>
3763
+ <stop offset="100%" stop-color="hsl(${(hue + 40) % 360}, 70%, 45%)"/>
3764
+ </linearGradient>
3765
+ </defs>
3766
+ <rect width="400" height="400" fill="url(#g)"/>
3767
+ <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>
3768
+ </svg>`;
3769
+ return new Response(svg, {
3770
+ headers: {
3771
+ "Content-Type": "image/svg+xml; charset=utf-8",
3772
+ ...NO_CACHE,
3773
+ "Access-Control-Allow-Origin": "*"
3774
+ }
3775
+ });
3776
+ }
3777
+ function handleImageRequest(c) {
3778
+ const path = c.req.path.replace(/^\/img\//, "").replace(/\.[a-z0-9]+$/i, "");
3779
+ const slug = path.split("/").pop() ?? "";
3780
+ for (const [k, v] of Object.entries(NO_CACHE)) c.header(k, v);
3781
+ if (!slug) return svgFallback("");
3782
+ const keyword = SLUG_KEYWORDS[slug] ?? slug.replace(/[-_]+/g, ",");
3783
+ const lock = lockFromSlug(slug);
3784
+ return c.redirect(
3785
+ `https://loremflickr.com/600/600/${encodeURIComponent(keyword)}?lock=${lock}`,
3786
+ 302
3787
+ );
3788
+ }
3789
+ function mountImageRoutes(app) {
3790
+ app.get("/img/*", handleImageRequest);
3791
+ }
3792
+
3621
3793
  // src/mock/core/clock.ts
3622
3794
  function createClock(opts) {
3623
3795
  let frozenAt = opts?.frozenAt ? opts.frozenAt.getTime() : null;
@@ -3955,36 +4127,249 @@ function percent(a, pct) {
3955
4127
  return toMajor(Math.round(toMinor(a) * pct / 100));
3956
4128
  }
3957
4129
 
3958
- // src/mock/domain/catalogue.ts
3959
- function createCatalogueService(opts) {
3960
- const { registry, clock, ids } = opts;
3961
- const productsForBusiness = (businessId) => registry.products.list((p) => p.business_id === businessId);
3962
- const toApiProduct = (p) => ({
4130
+ // src/mock/domain/catalogue/price-info.ts
4131
+ function defaultPriceInfo(price, currency) {
4132
+ return {
4133
+ base_price: price,
4134
+ final_price: price,
4135
+ pre_tax_price: price,
4136
+ markup_percentage: "0.00",
4137
+ markup_amount: "0.00",
4138
+ markup_discount_percentage: "0.00",
4139
+ markup_discount_amount: "0.00",
4140
+ currency,
4141
+ tax_info: {
4142
+ tax_rate: "0",
4143
+ tax_amount: "0.00",
4144
+ is_inclusive: true,
4145
+ pre_tax_price: price,
4146
+ components: []
4147
+ }
4148
+ };
4149
+ }
4150
+
4151
+ // src/mock/domain/catalogue/product-shape.ts
4152
+ function toCanonicalProductSummary(p) {
4153
+ return {
4154
+ id: p.id,
4155
+ business_id: p.business_id,
4156
+ category_id: p.category_ids[0],
4157
+ name: p.name,
4158
+ slug: p.slug,
4159
+ description: p.description,
4160
+ image_url: p.image,
4161
+ images: p.images,
4162
+ default_price: p.base_price,
4163
+ type: p.product_type,
4164
+ inventory_type: "none",
4165
+ variant_strategy: "fetch_all",
4166
+ is_active: p.is_available,
4167
+ inventory_status: { in_stock: p.is_available, stock_level: 50, low_stock: false },
4168
+ tags: p.tags ?? [],
4169
+ created_at: p.created_at,
4170
+ updated_at: p.updated_at
4171
+ };
4172
+ }
4173
+ function deriveRenderHint(p) {
4174
+ if (p.product_type === "service") return "general";
4175
+ const foodish = (p.tags ?? []).some(
4176
+ (t) => /food|drink|cake|bread|pastry|dessert|coffee|tea|juice|spice/i.test(t)
4177
+ );
4178
+ return foodish ? "food" : "general";
4179
+ }
4180
+ function toApiProduct(p, ctx) {
4181
+ const { registry, priceInfo, groups, components, composite, bundle } = ctx;
4182
+ return {
4183
+ // ─── Canonical Product fields (SDK type: Product / ProductWithDetails) ───
3963
4184
  id: p.id,
3964
4185
  business_id: p.business_id,
4186
+ category_id: p.category_ids[0],
3965
4187
  name: p.name,
3966
4188
  slug: p.slug,
3967
4189
  description: p.description ?? "",
3968
- product_type: p.product_type,
4190
+ image_url: p.image,
4191
+ images: p.images,
4192
+ default_price: p.base_price,
4193
+ type: p.product_type,
4194
+ render_hint: deriveRenderHint(p),
4195
+ inventory_type: "none",
4196
+ variant_strategy: p.variant_ids.length > 0 ? "use_axes" : "fetch_all",
4197
+ is_active: p.is_available,
4198
+ inventory_status: { in_stock: p.is_available, stock_level: 50, low_stock: false },
4199
+ tags: p.tags ?? [],
4200
+ calories: p.calories,
4201
+ allergies: p.allergies ?? [],
4202
+ created_at: p.created_at,
4203
+ updated_at: p.updated_at,
4204
+ metadata: {
4205
+ is_signature: p.is_signature ?? false,
4206
+ is_new: p.is_new ?? false,
4207
+ ingredients: p.ingredients ?? [],
4208
+ pairings: p.pairings ?? [],
4209
+ ...p.metadata ?? {}
4210
+ },
4211
+ // ─── Pricing breakdown (Price / ProductCustomizer) ───
3969
4212
  base_price: p.base_price,
4213
+ default_price_info: priceInfo,
4214
+ price_info: priceInfo,
3970
4215
  currency: p.currency,
4216
+ // ─── Compatibility aliases (older callers) ───
3971
4217
  image: p.image,
3972
- images: p.images,
4218
+ product_type: p.product_type,
3973
4219
  is_available: p.is_available,
3974
4220
  category_ids: p.category_ids,
3975
4221
  collection_ids: p.collection_ids,
3976
4222
  add_on_ids: p.add_on_ids,
3977
- metadata: p.metadata ?? {},
3978
- created_at: p.created_at,
3979
- updated_at: p.updated_at,
4223
+ variant_ids: p.variant_ids,
3980
4224
  custom_attributes: [],
3981
4225
  properties: [],
3982
4226
  has_variants: p.variant_ids.length > 0,
3983
- render_hint: "general"
3984
- });
4227
+ // ─── Embedded relations (ProductWithDetails) ───
4228
+ category: registry.categories.get(p.category_ids[0]),
4229
+ add_ons: p.add_on_ids.map((aid) => registry.addOns.get(aid)).filter(Boolean),
4230
+ variants: registry.variants.list((v) => v.product_id === p.id),
4231
+ variant_axes: registry.variantAxes.list((a) => a.product_id === p.id),
4232
+ // ─── Composite-specific (CompositeProductLayout reads product.groups) ───
4233
+ groups,
4234
+ composite_id: composite?.id,
4235
+ pricing_mode: composite?.pricing_mode,
4236
+ min_order_quantity: composite?.min_order_quantity,
4237
+ max_order_quantity: composite?.max_order_quantity,
4238
+ // ─── Bundle-specific (BundleProductLayout reads product.components) ───
4239
+ bundle_id: bundle?.id,
4240
+ bundle_pricing_type: bundle?.pricing_type,
4241
+ bundle_price: bundle?.bundle_price,
4242
+ bundle_discount_value: bundle?.discount_value,
4243
+ components
4244
+ };
4245
+ }
4246
+
4247
+ // src/mock/domain/catalogue/composite-shape.ts
4248
+ function expandCompositeGroups(registry, productId, isoNow) {
4249
+ const composite = registry.composites.list((c) => c.product_id === productId)[0];
4250
+ if (!composite) return void 0;
4251
+ return composite.groups.map((g, gIdx) => buildGroup(registry, composite, g, gIdx, isoNow));
4252
+ }
4253
+ function buildGroup(registry, composite, group, groupIdx, isoNow) {
4254
+ return {
4255
+ // ─── Canonical ComponentGroupWithComponents ───
4256
+ id: group.id,
4257
+ composite_id: composite.id,
4258
+ name: group.name,
4259
+ description: void 0,
4260
+ display_order: groupIdx,
4261
+ min_selections: group.min,
4262
+ max_selections: group.max,
4263
+ allow_quantity: true,
4264
+ max_quantity_per_component: group.max,
4265
+ pricing_behavior: "additive",
4266
+ created_at: isoNow,
4267
+ updated_at: isoNow,
4268
+ // ─── Aliases for older selectors ───
4269
+ min: group.min,
4270
+ max: group.max,
4271
+ 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))
4272
+ };
4273
+ }
4274
+ function buildComponent(composite, group, p, idx) {
4275
+ return {
4276
+ // ─── Canonical CompositeComponent ───
4277
+ id: `comp_${composite.product_id}_${p.id}`,
4278
+ composite_id: composite.id,
4279
+ group_id: group.id,
4280
+ source_type: "product",
4281
+ source_product_id: p.id,
4282
+ product_id: p.id,
4283
+ display_name: p.name,
4284
+ display_description: p.description,
4285
+ display_image_url: p.image,
4286
+ price: p.base_price,
4287
+ calories: p.calories,
4288
+ allergens: p.allergies ?? [],
4289
+ display_order: idx,
4290
+ is_popular: idx === 0,
4291
+ is_premium: false,
4292
+ is_available: p.is_available,
4293
+ is_archived: false,
4294
+ created_at: p.created_at,
4295
+ updated_at: p.updated_at,
4296
+ // ─── Compatibility aliases ───
4297
+ name: p.name,
4298
+ description: p.description,
4299
+ image_url: p.image,
4300
+ unit_price: p.base_price,
4301
+ default_price: p.base_price,
4302
+ base_price: p.base_price,
4303
+ currency: p.currency,
4304
+ product: toCanonicalProductSummary(p)
4305
+ };
4306
+ }
4307
+
4308
+ // src/mock/domain/catalogue/bundle-shape.ts
4309
+ function expandBundleComponents(registry, productId) {
4310
+ const bundle = registry.bundles.list((b) => b.product_id === productId)[0];
4311
+ if (!bundle) return void 0;
4312
+ return bundle.product_ids.map((cid) => registry.products.get(cid)).filter((p) => Boolean(p)).map((p) => buildBundleComponent(bundle.id, productId, p));
4313
+ }
4314
+ function buildBundleComponent(bundleId, productId, p) {
4315
+ return {
4316
+ id: `bp_${productId}_${p.id}`,
4317
+ bundle_id: bundleId,
4318
+ product_id: p.id,
4319
+ // ─── Fields BundleProductLayout reads ───
4320
+ product_name: p.name,
4321
+ product_image_url: p.image,
4322
+ product_description: p.description,
4323
+ product_slug: p.slug,
4324
+ effective_price: p.base_price,
4325
+ unit_price: p.base_price,
4326
+ default_price: p.base_price,
4327
+ quantity: 1,
4328
+ allow_variant_choice: false,
4329
+ available_variants: [],
4330
+ selected_variant_id: null,
4331
+ // ─── Compatibility aliases ───
4332
+ display_name: p.name,
4333
+ display_description: p.description,
4334
+ name: p.name,
4335
+ description: p.description,
4336
+ image_url: p.image,
4337
+ currency: p.currency,
4338
+ is_available: p.is_available,
4339
+ product: toCanonicalProductSummary(p)
4340
+ };
4341
+ }
4342
+
4343
+ // src/mock/domain/catalogue/index.ts
4344
+ function createCatalogueService(opts) {
4345
+ const { registry, clock, ids } = opts;
4346
+ const productsForBusiness = (businessId) => registry.products.list((p) => p.business_id === businessId);
3985
4347
  const variantsForProduct = (productId) => registry.variants.list((v) => v.product_id === productId);
3986
4348
  const axesForProduct = (productId) => registry.variantAxes.list((a) => a.product_id === productId);
3987
4349
  const addOnLinksForProduct = (productId) => registry.productAddOns.list((l) => l.product_id === productId);
4350
+ const apiProduct = (p) => {
4351
+ const composite = p.product_type === "composite" ? registry.composites.list((c) => c.product_id === p.id)[0] : void 0;
4352
+ const bundle = p.product_type === "bundle" ? registry.bundles.list((b) => b.product_id === p.id)[0] : void 0;
4353
+ return toApiProduct(p, {
4354
+ registry,
4355
+ priceInfo: defaultPriceInfo(p.base_price, p.currency),
4356
+ groups: expandCompositeGroups(registry, p.id, clock.iso()),
4357
+ components: expandBundleComponents(registry, p.id),
4358
+ composite: composite ? {
4359
+ id: composite.id,
4360
+ pricing_mode: composite.pricing_mode,
4361
+ min_order_quantity: composite.min_order_quantity,
4362
+ max_order_quantity: composite.max_order_quantity
4363
+ } : void 0,
4364
+ bundle: bundle ? {
4365
+ id: bundle.id,
4366
+ pricing_type: bundle.pricing_type,
4367
+ bundle_price: bundle.bundle_price,
4368
+ discount_value: bundle.discount_value
4369
+ } : void 0
4370
+ });
4371
+ };
4372
+ const resolveProduct = (idOrSlug) => registry.products.get(idOrSlug) ?? registry.products.list((p) => p.slug === idOrSlug)[0];
3988
4373
  return {
3989
4374
  snapshot(businessId) {
3990
4375
  const products = productsForBusiness(businessId);
@@ -3992,7 +4377,7 @@ function createCatalogueService(opts) {
3992
4377
  const addOns = registry.addOns.list((a) => a.business_id === businessId);
3993
4378
  return {
3994
4379
  categories,
3995
- products: products.map(toApiProduct),
4380
+ products: products.map(apiProduct),
3996
4381
  add_ons: addOns,
3997
4382
  is_complete: true,
3998
4383
  total_available: products.length
@@ -4007,7 +4392,7 @@ function createCatalogueService(opts) {
4007
4392
  const start = (page - 1) * limit;
4008
4393
  const slice = items.slice(start, start + limit);
4009
4394
  return {
4010
- items: slice.map(toApiProduct),
4395
+ items: slice.map(apiProduct),
4011
4396
  is_complete: start + limit >= items.length,
4012
4397
  total_available: items.length,
4013
4398
  pagination: {
@@ -4020,9 +4405,9 @@ function createCatalogueService(opts) {
4020
4405
  };
4021
4406
  },
4022
4407
  getProduct(productId) {
4023
- const p = registry.products.get(productId);
4408
+ const p = resolveProduct(productId);
4024
4409
  if (!p) throw notFound(`Product ${productId} not found`);
4025
- return toApiProduct(p);
4410
+ return apiProduct(p);
4026
4411
  },
4027
4412
  getProductVariants(productId) {
4028
4413
  return variantsForProduct(productId);
@@ -4032,7 +4417,9 @@ function createCatalogueService(opts) {
4032
4417
  },
4033
4418
  getProductVariantById(productId, variantId) {
4034
4419
  const v = registry.variants.get(variantId);
4035
- if (!v || v.product_id !== productId) throw notFound(`Variant ${variantId} not found on product ${productId}`);
4420
+ if (!v || v.product_id !== productId) {
4421
+ throw notFound(`Variant ${variantId} not found on product ${productId}`);
4422
+ }
4036
4423
  return v;
4037
4424
  },
4038
4425
  findProductVariant(productId, properties) {
@@ -4062,13 +4449,13 @@ function createCatalogueService(opts) {
4062
4449
  return registry.categories.list((c) => c.business_id === businessId);
4063
4450
  },
4064
4451
  getCategory(id) {
4065
- const c = registry.categories.get(id);
4452
+ const c = registry.categories.get(id) ?? registry.categories.list((x) => x.slug === id)[0];
4066
4453
  if (!c) throw notFound(`Category ${id} not found`);
4067
4454
  return c;
4068
4455
  },
4069
4456
  getCategoryProducts(id) {
4070
4457
  const c = this.getCategory(id);
4071
- return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(toApiProduct);
4458
+ return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(apiProduct);
4072
4459
  },
4073
4460
  getCategoryAttributes(id) {
4074
4461
  const c = registry.categories.get(id);
@@ -4081,13 +4468,13 @@ function createCatalogueService(opts) {
4081
4468
  return registry.collections.list((c) => c.business_id === businessId);
4082
4469
  },
4083
4470
  getCollection(id) {
4084
- const c = registry.collections.get(id);
4471
+ const c = registry.collections.get(id) ?? registry.collections.list((x) => x.slug === id)[0];
4085
4472
  if (!c) throw notFound(`Collection ${id} not found`);
4086
4473
  return c;
4087
4474
  },
4088
4475
  getCollectionProducts(id) {
4089
4476
  const c = this.getCollection(id);
4090
- return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(toApiProduct);
4477
+ return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(apiProduct);
4091
4478
  },
4092
4479
  getCollectionAttributes(id) {
4093
4480
  const c = registry.collections.get(id);
@@ -4100,7 +4487,7 @@ function createCatalogueService(opts) {
4100
4487
  return registry.bundles.list((b) => b.business_id === businessId);
4101
4488
  },
4102
4489
  getBundle(id) {
4103
- const b = registry.bundles.get(id);
4490
+ const b = registry.bundles.get(id) ?? registry.bundles.list((x) => x.product_id === id || x.slug === id)[0];
4104
4491
  if (!b) throw notFound(`Bundle ${id} not found`);
4105
4492
  return b;
4106
4493
  },
@@ -4108,12 +4495,16 @@ function createCatalogueService(opts) {
4108
4495
  return registry.composites.list((c) => c.business_id === businessId);
4109
4496
  },
4110
4497
  getComposite(id) {
4111
- const c = registry.composites.get(id);
4498
+ const c = registry.composites.get(id) ?? registry.composites.list((x) => x.product_id === id)[0];
4112
4499
  if (!c) throw notFound(`Composite ${id} not found`);
4113
4500
  return c;
4114
4501
  },
4115
4502
  getCompositeByProduct(productId) {
4116
- return registry.composites.list((c) => c.product_id === productId)[0] ?? null;
4503
+ const direct = registry.composites.list((c) => c.product_id === productId)[0];
4504
+ if (direct) return direct;
4505
+ const product = registry.products.list((p) => p.slug === productId)[0];
4506
+ if (!product) return null;
4507
+ return registry.composites.list((c) => c.product_id === product.id)[0] ?? null;
4117
4508
  },
4118
4509
  calculateCompositePrice(id, body) {
4119
4510
  const c = registry.composites.get(id);
@@ -4121,14 +4512,20 @@ function createCatalogueService(opts) {
4121
4512
  const sel = body.selections ?? [];
4122
4513
  let total = c.base_price;
4123
4514
  for (const s of sel) total = add(total, money(parseFloat(s.unit_price ?? "0") * (s.quantity ?? 1)));
4124
- return { final_price: total, base_price: c.base_price, components_total: money(parseFloat(total) - parseFloat(c.base_price)) };
4515
+ return {
4516
+ final_price: total,
4517
+ base_price: c.base_price,
4518
+ components_total: money(parseFloat(total) - parseFloat(c.base_price))
4519
+ };
4125
4520
  },
4126
4521
  listTaxonomies(businessId) {
4127
4522
  return registry.taxonomies.list((t) => t.business_id === businessId);
4128
4523
  },
4129
4524
  searchTaxonomies(businessId, q) {
4130
4525
  const term = q.toLowerCase();
4131
- return registry.taxonomies.list((t) => t.business_id === businessId && t.name.toLowerCase().includes(term));
4526
+ return registry.taxonomies.list(
4527
+ (t) => t.business_id === businessId && t.name.toLowerCase().includes(term)
4528
+ );
4132
4529
  },
4133
4530
  getTaxonomy(id) {
4134
4531
  const t = registry.taxonomies.get(id);
@@ -4162,7 +4559,11 @@ function createCatalogueService(opts) {
4162
4559
  listPropertyFacets(businessId) {
4163
4560
  const products = productsForBusiness(businessId);
4164
4561
  const byCategory = /* @__PURE__ */ new Map();
4165
- for (const p of products) for (const cid of p.category_ids) byCategory.set(cid, (byCategory.get(cid) ?? 0) + 1);
4562
+ for (const p of products) {
4563
+ for (const cid of p.category_ids) {
4564
+ byCategory.set(cid, (byCategory.get(cid) ?? 0) + 1);
4565
+ }
4566
+ }
4166
4567
  return [
4167
4568
  {
4168
4569
  name: "Category",
@@ -4181,12 +4582,6 @@ function createCatalogueService(opts) {
4181
4582
  listOnSale() {
4182
4583
  return [];
4183
4584
  },
4184
- validateDiscount(code) {
4185
- if (code === "WELCOME10") {
4186
- return { valid: true, discount_id: "disc_welcome10", discount_value: "10.00", discount_type: "percentage" };
4187
- }
4188
- return { valid: false, reason: "Code not recognized" };
4189
- },
4190
4585
  getProductDeals() {
4191
4586
  return [];
4192
4587
  },
@@ -4196,15 +4591,33 @@ function createCatalogueService(opts) {
4196
4591
  getCollectionDeals() {
4197
4592
  return [];
4198
4593
  },
4594
+ validateDiscount(code) {
4595
+ if (code === "WELCOME10") {
4596
+ return { valid: true, discount_id: "disc_welcome10", discount_value: "10.00", discount_type: "percentage" };
4597
+ }
4598
+ return { valid: false, reason: "Code not recognized" };
4599
+ },
4199
4600
  createQuote(businessId, body) {
4200
4601
  const b = body;
4201
4602
  const id = ids.quote();
4603
+ const productId = b.product_id ?? "";
4604
+ const product = resolveProduct(productId);
4605
+ const price = b.total_price ?? product?.base_price ?? "0.00";
4606
+ const currency = b.currency ?? product?.currency ?? "GHS";
4607
+ const priceInfo = defaultPriceInfo(price, currency);
4202
4608
  const quote = {
4203
4609
  id,
4610
+ quote_id: id,
4204
4611
  business_id: businessId,
4205
- product_id: b.product_id ?? "",
4612
+ product_id: product?.id ?? productId,
4206
4613
  status: "fresh",
4207
- total_price: b.total_price ?? "0.00",
4614
+ total_price: price,
4615
+ currency,
4616
+ unit_price_info: priceInfo,
4617
+ final_price_info: priceInfo,
4618
+ quoted_total_price_info: priceInfo,
4619
+ components: [],
4620
+ breakdown: { base_price: price, components_total: "0.00", final_price: price },
4208
4621
  expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString(),
4209
4622
  created_at: clock.iso()
4210
4623
  };
@@ -4220,7 +4633,11 @@ function createCatalogueService(opts) {
4220
4633
  refreshQuote(id) {
4221
4634
  const q = registry.quotes.get(id);
4222
4635
  if (!q) throw notFound(`Quote ${id} not found`);
4223
- const refreshed = { ...q, status: "fresh", expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString() };
4636
+ const refreshed = {
4637
+ ...q,
4638
+ status: "fresh",
4639
+ expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString()
4640
+ };
4224
4641
  registry.quotes.put(id, refreshed);
4225
4642
  return refreshed;
4226
4643
  }
@@ -5245,67 +5662,82 @@ function createLiteService(opts) {
5245
5662
 
5246
5663
  // src/mock/seeds/default.ts
5247
5664
  var ISO = "2026-01-01T00:00:00.000Z";
5248
- var PRODUCTS = [
5249
- { 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" },
5250
- { 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" },
5251
- { name: "Baguette", slug: "baguette", price: "15.00", category: "breads", description: "Crispy crust, soft inside.", image: "https://mock.cimplify.io/img/baguette.jpg" },
5252
- { name: "Focaccia", slug: "focaccia", price: "30.00", category: "breads", description: "Olive oil and rosemary focaccia.", image: "https://mock.cimplify.io/img/focaccia.jpg" },
5253
- { name: "Croissant", slug: "croissant", price: "12.00", category: "pastries", description: "Buttery French croissant.", image: "https://mock.cimplify.io/img/croissant.jpg" },
5254
- { 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" },
5255
- { 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" },
5256
- { 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" },
5257
- { 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" },
5258
- { 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" },
5259
- { 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" },
5260
- { name: "Cheesecake (Slice)", slug: "cheesecake", price: "24.00", category: "cakes", description: "New York style cheesecake.", image: "https://mock.cimplify.io/img/cheesecake.jpg" },
5261
- { name: "Coffee", slug: "coffee", price: "10.00", category: "drinks", description: "Locally roasted Arabica.", image: "https://mock.cimplify.io/img/coffee.jpg" },
5262
- { name: "Tea", slug: "tea", price: "8.00", category: "drinks", description: "Black, green, or herbal.", image: "https://mock.cimplify.io/img/tea.jpg" },
5263
- { name: "Hibiscus Tea (Sobolo)", slug: "sobolo", price: "12.00", category: "drinks", description: "Iced hibiscus drink.", image: "https://mock.cimplify.io/img/sobolo.jpg" },
5264
- { 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" },
5265
- { name: "Banana Bread", slug: "banana-bread", price: "18.00", category: "breads", description: "Moist banana loaf.", image: "https://mock.cimplify.io/img/banana.jpg" },
5266
- { name: "Brownie", slug: "brownie", price: "12.00", category: "pastries", description: "Fudgy chocolate brownie.", image: "https://mock.cimplify.io/img/brownie.jpg" },
5267
- { name: "Macaron Box (6)", slug: "macaron-box", price: "60.00", category: "pastries", description: "Six assorted macarons.", image: "https://mock.cimplify.io/img/macaron.jpg" },
5268
- { 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" }
5269
- ];
5270
- var CATEGORIES = [
5271
- { slug: "breads", name: "Breads", description: "Sourdough, baguettes, focaccia." },
5272
- { slug: "pastries", name: "Pastries", description: "Croissants, danishes, brownies." },
5273
- { slug: "cakes", name: "Cakes", description: "Cake slices and custom orders." },
5274
- { slug: "drinks", name: "Drinks", description: "Coffee, tea, juices." }
5275
- ];
5665
+ var BUSINESS_ID = "bus_default_akua_bakery";
5666
+ var img = (id) => `https://images.unsplash.com/photo-${id}?w=800&h=800&fit=crop&auto=format&q=80`;
5667
+ function makeProduct(input, categoryIds) {
5668
+ const catId = categoryIds.get(input.category);
5669
+ return {
5670
+ id: input.id,
5671
+ business_id: BUSINESS_ID,
5672
+ name: input.name,
5673
+ slug: input.slug,
5674
+ description: input.description,
5675
+ product_type: input.productType ?? "product",
5676
+ base_price: input.price,
5677
+ currency: "GHS",
5678
+ image: input.imageUrl,
5679
+ images: input.images ?? [input.imageUrl],
5680
+ is_available: true,
5681
+ category_ids: [catId],
5682
+ collection_ids: [],
5683
+ add_on_ids: [],
5684
+ variant_ids: [],
5685
+ tags: input.tags ?? [],
5686
+ calories: input.calories,
5687
+ allergies: input.allergies ?? [],
5688
+ ingredients: input.ingredients ?? [],
5689
+ pairings: input.pairings ?? [],
5690
+ is_signature: input.isSignature ?? false,
5691
+ is_new: input.isNew ?? false,
5692
+ created_at: ISO,
5693
+ updated_at: ISO
5694
+ };
5695
+ }
5276
5696
  function seedDefault(registry) {
5277
- const businessId = "bus_default_akua_bakery";
5278
5697
  const business = {
5279
- id: businessId,
5698
+ id: BUSINESS_ID,
5280
5699
  name: "Akua's Bakery",
5281
5700
  handle: "akua",
5282
5701
  business_type: "eatery",
5283
5702
  email: "hello@akuasbakery.test",
5284
5703
  default_currency: "GHS",
5285
- default_phone: "+233244000000",
5704
+ default_phone: "+233 244 000 000",
5286
5705
  default_address: "12 Independence Ave, Accra",
5287
5706
  default_offers_table_service: true,
5288
5707
  default_accepts_online_orders: true,
5289
- image: "https://mock.cimplify.io/img/akua-logo.png",
5708
+ image: img("1568254183919-78a4f43a2877"),
5290
5709
  status: "active",
5291
5710
  created_at: ISO,
5292
5711
  updated_at: ISO,
5293
5712
  created_by: "seed",
5294
- preferences: { theme: "warm", checkout: { allow_guest: true } },
5713
+ preferences: {
5714
+ tagline: "Fresh from the oven, every day",
5715
+ description: "Family-run bakery in East Legon. Sourdough, croissants, layered cakes \u2014 handcrafted since 2018, delivered across Accra.",
5716
+ instagram: "https://instagram.com/akuasbakery",
5717
+ theme: "warm",
5718
+ checkout: { allow_guest: true }
5719
+ },
5295
5720
  is_online_only: false,
5296
5721
  enabled_payment_types: ["mobile_money", "card", "cash"],
5297
5722
  default_location_settings: {},
5298
5723
  country_code: "GH",
5299
5724
  timezone: "Africa/Accra"
5300
5725
  };
5301
- registry.businesses.put(businessId, business);
5302
- const categoryIdsBySlug = /* @__PURE__ */ new Map();
5303
- for (const c of CATEGORIES) {
5726
+ registry.businesses.put(BUSINESS_ID, business);
5727
+ const categoryDefs = [
5728
+ { slug: "breads", name: "Breads", description: "Naturally leavened sourdough, baguettes, focaccia." },
5729
+ { slug: "pastries", name: "Pastries", description: "Hand-laminated croissants, danishes, brownies." },
5730
+ { slug: "cakes", name: "Cakes", description: "Slices and made-to-order showstoppers." },
5731
+ { slug: "drinks", name: "Drinks", description: "Locally roasted coffee, brewed teas, fresh juice." },
5732
+ { slug: "boxes", name: "Boxes & Bundles", description: "Curated boxes for sharing." }
5733
+ ];
5734
+ const catIds = /* @__PURE__ */ new Map();
5735
+ for (const c of categoryDefs) {
5304
5736
  const id = `cat_${c.slug}`;
5305
- categoryIdsBySlug.set(c.slug, id);
5737
+ catIds.set(c.slug, id);
5306
5738
  const cat = {
5307
5739
  id,
5308
- business_id: businessId,
5740
+ business_id: BUSINESS_ID,
5309
5741
  name: c.name,
5310
5742
  slug: c.slug,
5311
5743
  description: c.description,
@@ -5317,99 +5749,354 @@ function seedDefault(registry) {
5317
5749
  };
5318
5750
  registry.categories.put(id, cat);
5319
5751
  }
5320
- const featured = [];
5321
- PRODUCTS.forEach((p, i) => {
5322
- const id = `prod_${p.slug}`;
5323
- const catId = categoryIdsBySlug.get(p.category);
5324
- const product = {
5325
- id,
5326
- business_id: businessId,
5327
- name: p.name,
5328
- slug: p.slug,
5329
- description: p.description,
5330
- product_type: "product",
5331
- base_price: p.price,
5332
- currency: "GHS",
5333
- image: p.image,
5334
- images: [p.image],
5335
- is_available: true,
5336
- category_ids: [catId],
5337
- collection_ids: [],
5338
- add_on_ids: [],
5339
- variant_ids: [],
5340
- created_at: ISO,
5341
- updated_at: ISO
5342
- };
5343
- registry.products.put(id, product);
5344
- const cat = registry.categories.get(catId);
5345
- cat.product_ids.push(id);
5346
- registry.categories.put(catId, cat);
5347
- if (i < 5) featured.push(id);
5348
- });
5349
- const featuredCollection = {
5752
+ const productInputs = [
5753
+ // ── Breads ──
5754
+ {
5755
+ id: "prod_sourdough-loaf",
5756
+ name: "Country Sourdough",
5757
+ slug: "sourdough-loaf",
5758
+ price: "25.00",
5759
+ description: "24-hour fermented country loaf with our heritage starter \u2014 open crumb, blistered crust, naturally tangy. Baked fresh every morning.",
5760
+ category: "breads",
5761
+ imageUrl: img("1509440159596-0249088772ff"),
5762
+ images: [img("1509440159596-0249088772ff"), img("1486887396153-fa416526c108")],
5763
+ tags: ["bread", "vegan", "signature"],
5764
+ isSignature: true,
5765
+ ingredients: ["Wheat flour", "Sourdough starter", "Sea salt", "Filtered water"],
5766
+ pairings: ["Olive oil", "Aged cheese", "Tomato jam"],
5767
+ allergies: ["wheat", "gluten"],
5768
+ calories: 240
5769
+ },
5770
+ {
5771
+ id: "prod_whole-wheat",
5772
+ name: "Whole Wheat Seed Loaf",
5773
+ slug: "whole-wheat",
5774
+ price: "20.00",
5775
+ description: "Hearty 100% whole wheat with sunflower, sesame, and pumpkin seeds. Slow-fermented for a soft crumb and nutty depth.",
5776
+ category: "breads",
5777
+ imageUrl: img("1486887396153-fa416526c108"),
5778
+ tags: ["bread", "vegan", "high-fibre"],
5779
+ ingredients: ["Whole wheat flour", "Sunflower seeds", "Sesame", "Pumpkin seeds"],
5780
+ allergies: ["wheat", "gluten", "sesame"],
5781
+ calories: 220
5782
+ },
5783
+ {
5784
+ id: "prod_baguette",
5785
+ name: "Classic Baguette",
5786
+ slug: "baguette",
5787
+ price: "15.00",
5788
+ description: "Crackling crust, airy crumb. Best eaten warm, within an hour of baking.",
5789
+ category: "breads",
5790
+ imageUrl: img("1568471173242-461f0a730452"),
5791
+ tags: ["bread", "vegan"],
5792
+ ingredients: ["Wheat flour", "Yeast", "Sea salt", "Water"],
5793
+ allergies: ["wheat", "gluten"],
5794
+ calories: 180
5795
+ },
5796
+ {
5797
+ id: "prod_focaccia",
5798
+ name: "Rosemary Focaccia",
5799
+ slug: "focaccia",
5800
+ price: "30.00",
5801
+ description: "High-hydration focaccia drenched in olive oil, scattered with sea salt and fresh rosemary. Sold whole or by the slab.",
5802
+ category: "breads",
5803
+ imageUrl: img("1571167530149-c1105da4c2c7"),
5804
+ tags: ["bread", "vegetarian"],
5805
+ ingredients: ["00 flour", "Olive oil", "Rosemary", "Maldon salt"],
5806
+ pairings: ["Burrata", "Prosciutto", "Caponata"],
5807
+ allergies: ["wheat", "gluten"],
5808
+ calories: 310
5809
+ },
5810
+ {
5811
+ id: "prod_banana-bread",
5812
+ name: "Brown Butter Banana Bread",
5813
+ slug: "banana-bread",
5814
+ price: "18.00",
5815
+ description: "Caramelised brown butter, three over-ripe bananas, walnuts, finished with a salted maple glaze.",
5816
+ category: "breads",
5817
+ imageUrl: img("1632931057819-4eefffa8e007"),
5818
+ tags: ["sweet", "comfort"],
5819
+ ingredients: ["Bananas", "Brown butter", "Walnuts", "Maple syrup"],
5820
+ allergies: ["wheat", "gluten", "dairy", "nuts", "eggs"],
5821
+ calories: 360,
5822
+ isNew: true
5823
+ },
5824
+ // ── Pastries ──
5825
+ {
5826
+ id: "prod_croissant",
5827
+ name: "Butter Croissant",
5828
+ slug: "croissant",
5829
+ price: "12.00",
5830
+ description: "27 layers of laminated dough wrapped around French butter. Crisp, shattering crust and a tender honeycomb interior.",
5831
+ category: "pastries",
5832
+ imageUrl: img("1555507036-ab1f4038808a"),
5833
+ images: [img("1555507036-ab1f4038808a"), img("1530610476181-d83430b64dcd")],
5834
+ tags: ["pastry", "vegetarian", "signature"],
5835
+ isSignature: true,
5836
+ ingredients: ["French butter", "Wheat flour", "Milk", "Eggs"],
5837
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5838
+ calories: 280
5839
+ },
5840
+ {
5841
+ id: "prod_pain-au-chocolat",
5842
+ name: "Pain au Chocolat",
5843
+ slug: "pain-au-chocolat",
5844
+ price: "14.00",
5845
+ description: "Our croissant dough wrapped around two batons of 70% Valrhona dark chocolate.",
5846
+ category: "pastries",
5847
+ imageUrl: img("1530610476181-d83430b64dcd"),
5848
+ tags: ["pastry", "chocolate"],
5849
+ ingredients: ["Croissant dough", "Valrhona chocolate"],
5850
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5851
+ calories: 320
5852
+ },
5853
+ {
5854
+ id: "prod_almond-danish",
5855
+ name: "Almond Danish",
5856
+ slug: "almond-danish",
5857
+ price: "16.00",
5858
+ description: "Flaky pastry filled with almond cream, dusted with toasted slivers and powdered sugar.",
5859
+ category: "pastries",
5860
+ imageUrl: img("1517433367423-c7e5b0f35086"),
5861
+ tags: ["pastry", "nuts"],
5862
+ ingredients: ["Pastry dough", "Almond cream", "Toasted almonds"],
5863
+ allergies: ["wheat", "gluten", "dairy", "eggs", "nuts"],
5864
+ calories: 340
5865
+ },
5866
+ {
5867
+ id: "prod_cinnamon-roll",
5868
+ name: "Cinnamon Roll",
5869
+ slug: "cinnamon-roll",
5870
+ price: "13.00",
5871
+ description: "Soft enriched dough swirled with butter and Ceylon cinnamon, finished with cream-cheese glaze.",
5872
+ category: "pastries",
5873
+ imageUrl: img("1607920591413-4ec007e70023"),
5874
+ tags: ["pastry", "comfort"],
5875
+ ingredients: ["Enriched dough", "Cinnamon", "Brown sugar", "Cream cheese"],
5876
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5877
+ calories: 380
5878
+ },
5879
+ {
5880
+ id: "prod_brownie",
5881
+ name: "Fudge Brownie",
5882
+ slug: "brownie",
5883
+ price: "12.00",
5884
+ description: "Dark, dense, fudgy brownie with a glossy shell. Chocolate four ways. Best slightly warm.",
5885
+ category: "pastries",
5886
+ imageUrl: img("1606313564200-e75d5e30476c"),
5887
+ tags: ["chocolate", "vegetarian"],
5888
+ ingredients: ["Dark chocolate", "Cocoa", "Brown butter", "Eggs"],
5889
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5890
+ calories: 420
5891
+ },
5892
+ {
5893
+ id: "prod_macaron-box",
5894
+ name: "Macaron Box (6)",
5895
+ slug: "macaron-box",
5896
+ price: "60.00",
5897
+ description: "Six French macarons \u2014 pistachio, raspberry, salted caramel, espresso, vanilla bean, and dark chocolate ganache.",
5898
+ category: "pastries",
5899
+ imageUrl: img("1558326567-98ae2405596b"),
5900
+ tags: ["pastry", "gift", "premium"],
5901
+ ingredients: ["Almond flour", "Egg whites", "Sugar", "Various fillings"],
5902
+ allergies: ["nuts", "eggs", "dairy"],
5903
+ calories: 540
5904
+ },
5905
+ // ── Cakes ──
5906
+ {
5907
+ id: "prod_chocolate-cake",
5908
+ name: "Dark Chocolate Layer Cake",
5909
+ slug: "chocolate-cake",
5910
+ price: "22.00",
5911
+ description: "Three layers of dark chocolate sponge with chocolate ganache and cr\xE8me p\xE2tissi\xE8re, finished with a mirror glaze. (Slice)",
5912
+ category: "cakes",
5913
+ imageUrl: img("1565958011703-44f9829ba187"),
5914
+ tags: ["cake", "chocolate", "signature"],
5915
+ isSignature: true,
5916
+ ingredients: ["Dark chocolate", "Cr\xE8me p\xE2tissi\xE8re", "Cocoa", "Eggs"],
5917
+ pairings: ["Espresso", "Cabernet"],
5918
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5919
+ calories: 480
5920
+ },
5921
+ {
5922
+ id: "prod_red-velvet",
5923
+ name: "Red Velvet (Slice)",
5924
+ slug: "red-velvet",
5925
+ price: "22.00",
5926
+ description: "Classic red velvet with cream cheese frosting and white chocolate shavings.",
5927
+ category: "cakes",
5928
+ imageUrl: img("1586788680434-30d324b2d46f"),
5929
+ tags: ["cake"],
5930
+ ingredients: ["Buttermilk", "Cocoa", "Cream cheese", "Beetroot"],
5931
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5932
+ calories: 460
5933
+ },
5934
+ {
5935
+ id: "prod_carrot-cake",
5936
+ name: "Carrot Cake (Slice)",
5937
+ slug: "carrot-cake",
5938
+ price: "20.00",
5939
+ description: "Spiced carrot cake with toasted walnuts, golden raisins, and cream-cheese frosting.",
5940
+ category: "cakes",
5941
+ imageUrl: img("1568827999250-3f6afff96e66"),
5942
+ tags: ["cake", "spiced"],
5943
+ ingredients: ["Carrot", "Walnuts", "Cinnamon", "Cream cheese"],
5944
+ allergies: ["wheat", "gluten", "dairy", "eggs", "nuts"],
5945
+ calories: 420
5946
+ },
5947
+ {
5948
+ id: "prod_cheesecake",
5949
+ name: "New York Cheesecake (Slice)",
5950
+ slug: "cheesecake",
5951
+ price: "24.00",
5952
+ description: "Dense, creamy New York cheesecake on a butter graham crust. Topped with seasonal berry compote.",
5953
+ category: "cakes",
5954
+ imageUrl: img("1567306226416-28f0efdc88ce"),
5955
+ tags: ["cake", "premium"],
5956
+ ingredients: ["Cream cheese", "Sour cream", "Graham crust", "Vanilla"],
5957
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5958
+ calories: 520
5959
+ },
5960
+ {
5961
+ id: "prod_birthday-cake",
5962
+ name: "Custom Celebration Cake",
5963
+ slug: "birthday-cake",
5964
+ price: "250.00",
5965
+ description: "Made-to-order three-tier cake. Choose size, flavour, and frosting. 48-hour notice required for personalisation.",
5966
+ category: "cakes",
5967
+ imageUrl: img("1535254973040-607b474cb50d"),
5968
+ images: [img("1535254973040-607b474cb50d"), img("1565958011703-44f9829ba187")],
5969
+ tags: ["cake", "made-to-order", "premium"],
5970
+ ingredients: ["Sponge cake", "Buttercream", "Custom decoration"],
5971
+ allergies: ["wheat", "gluten", "dairy", "eggs"],
5972
+ productType: "product"
5973
+ },
5974
+ // ── Drinks ──
5975
+ {
5976
+ id: "prod_coffee",
5977
+ name: "Single-Origin Coffee",
5978
+ slug: "coffee",
5979
+ price: "10.00",
5980
+ description: "Locally roasted Arabica from Volta Region. Notes of cocoa, dark fruit, and brown sugar.",
5981
+ category: "drinks",
5982
+ imageUrl: img("1495474472287-4d71bcdd2085"),
5983
+ tags: ["drink", "coffee", "vegan"],
5984
+ ingredients: ["Single-origin Arabica beans"],
5985
+ pairings: ["Croissant", "Brownie"],
5986
+ calories: 5
5987
+ },
5988
+ {
5989
+ id: "prod_tea",
5990
+ name: "Loose-Leaf Tea",
5991
+ slug: "tea",
5992
+ price: "8.00",
5993
+ description: "Choice of English breakfast, Earl Grey, green sencha, or chamomile.",
5994
+ category: "drinks",
5995
+ imageUrl: img("1576092768241-dec231879fc3"),
5996
+ tags: ["drink", "tea", "vegan"],
5997
+ calories: 0
5998
+ },
5999
+ {
6000
+ id: "prod_sobolo",
6001
+ name: "Hibiscus (Sobolo)",
6002
+ slug: "sobolo",
6003
+ price: "12.00",
6004
+ description: "Iced hibiscus tea with ginger, pineapple peel, and a hint of clove. Tart, refreshing, lightly sweet.",
6005
+ category: "drinks",
6006
+ imageUrl: img("1556679343-c7306c1976bc"),
6007
+ tags: ["drink", "vegan", "ghana"],
6008
+ isSignature: true,
6009
+ ingredients: ["Hibiscus", "Ginger", "Pineapple", "Clove"],
6010
+ calories: 60
6011
+ },
6012
+ {
6013
+ id: "prod_orange-juice",
6014
+ name: "Cold-Pressed Orange Juice",
6015
+ slug: "orange-juice",
6016
+ price: "15.00",
6017
+ description: "Hand-pressed at sunrise from Ghanaian Valencia oranges. No sugar, no concentrate.",
6018
+ category: "drinks",
6019
+ imageUrl: img("1622597467836-f3285f2131b8"),
6020
+ tags: ["drink", "vegan"],
6021
+ calories: 110
6022
+ },
6023
+ // ── Boxes (anchor products for bundle/composite) ──
6024
+ {
6025
+ id: "prod_breakfast-box",
6026
+ name: "Breakfast Box",
6027
+ slug: "breakfast-box",
6028
+ price: "30.00",
6029
+ 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.",
6030
+ category: "boxes",
6031
+ imageUrl: img("1533089860892-a7c6f0a88666"),
6032
+ images: [img("1533089860892-a7c6f0a88666"), img("1495474472287-4d71bcdd2085")],
6033
+ tags: ["bundle", "breakfast"],
6034
+ productType: "bundle"
6035
+ },
6036
+ {
6037
+ id: "prod_pastry-box",
6038
+ name: "Build-Your-Own Pastry Box",
6039
+ slug: "pastry-box",
6040
+ price: "60.00",
6041
+ description: "Pick 6 to 12 of our pastries \u2014 mix and match. Freshly boxed for sharing.",
6042
+ category: "boxes",
6043
+ imageUrl: img("1486427944299-d1955d23e34d"),
6044
+ tags: ["composite", "build-your-own", "gift"],
6045
+ productType: "composite"
6046
+ }
6047
+ ];
6048
+ for (const input of productInputs) {
6049
+ const product = makeProduct(input, catIds);
6050
+ registry.products.put(product.id, product);
6051
+ const cat = registry.categories.get(catIds.get(input.category));
6052
+ if (cat) {
6053
+ cat.product_ids.push(product.id);
6054
+ registry.categories.put(cat.id, cat);
6055
+ }
6056
+ }
6057
+ const featuredIds = [
6058
+ "prod_sourdough-loaf",
6059
+ "prod_croissant",
6060
+ "prod_chocolate-cake",
6061
+ "prod_sobolo",
6062
+ "prod_pastry-box"
6063
+ ];
6064
+ const featured = {
5350
6065
  id: "col_featured",
5351
- business_id: businessId,
5352
- name: "Featured",
6066
+ business_id: BUSINESS_ID,
6067
+ name: "Signature & Featured",
5353
6068
  slug: "featured",
5354
- description: "Our most popular items.",
5355
- product_ids: featured,
6069
+ description: "Our most-loved bakes and a few seasonal stars.",
6070
+ product_ids: featuredIds,
5356
6071
  is_active: true,
5357
6072
  created_at: ISO,
5358
6073
  updated_at: ISO
5359
6074
  };
5360
- registry.collections.put(featuredCollection.id, featuredCollection);
5361
- for (const pid of featured) {
6075
+ registry.collections.put(featured.id, featured);
6076
+ for (const pid of featuredIds) {
5362
6077
  const p = registry.products.get(pid);
5363
6078
  if (p) {
5364
- p.collection_ids.push(featuredCollection.id);
6079
+ p.collection_ids.push(featured.id);
5365
6080
  registry.products.put(pid, p);
5366
6081
  }
5367
6082
  }
5368
- const tasting = {
5369
- id: "svc_tasting",
5370
- business_id: businessId,
5371
- name: "Bakery Tasting Session",
5372
- description: "Walk through our pastry menu with our head baker.",
5373
- duration_minutes: 60,
5374
- price: "150.00",
5375
- is_available: true
5376
- };
5377
- registry.services.put(tasting.id, tasting);
5378
- const cake = {
5379
- id: "svc_custom_cake",
5380
- business_id: businessId,
5381
- name: "Custom Cake Consultation",
5382
- description: "Design a custom cake for your event.",
5383
- duration_minutes: 30,
5384
- price: "0.00",
5385
- is_available: true
5386
- };
5387
- registry.services.put(cake.id, cake);
5388
- const sizeAxisId = "axis_size";
5389
- const flavourAxisId = "axis_flavour";
5390
- const sizeValues = [
5391
- { id: "axv_size_small", name: 'Small (8")' },
5392
- { id: "axv_size_medium", name: 'Medium (10")' },
5393
- { id: "axv_size_large", name: 'Large (12")' }
5394
- ];
5395
- const flavourValues = [
5396
- { id: "axv_flavour_chocolate", name: "Chocolate" },
5397
- { id: "axv_flavour_vanilla", name: "Vanilla" },
5398
- { id: "axv_flavour_red_velvet", name: "Red Velvet" }
5399
- ];
5400
6083
  const sizeAxis = {
5401
- id: sizeAxisId,
5402
- business_id: businessId,
6084
+ id: "axis_cake_size",
6085
+ business_id: BUSINESS_ID,
5403
6086
  product_id: "prod_birthday-cake",
5404
6087
  name: "Size",
5405
6088
  display_order: 0,
5406
6089
  affects_recipe: false,
5407
- values: sizeValues.map((v, i) => ({
6090
+ values: [
6091
+ { id: "axv_size_small", name: 'Small (8")', display: 0 },
6092
+ { id: "axv_size_medium", name: 'Medium (10")', display: 1 },
6093
+ { id: "axv_size_large", name: 'Large (12")', display: 2 }
6094
+ ].map((v) => ({
5408
6095
  id: v.id,
5409
- business_id: businessId,
5410
- axis_id: sizeAxisId,
6096
+ business_id: BUSINESS_ID,
6097
+ axis_id: "axis_cake_size",
5411
6098
  name: v.name,
5412
- display_order: i,
6099
+ display_order: v.display,
5413
6100
  created_at: ISO,
5414
6101
  updated_at: ISO
5415
6102
  })),
@@ -5417,18 +6104,22 @@ function seedDefault(registry) {
5417
6104
  updated_at: ISO
5418
6105
  };
5419
6106
  const flavourAxis = {
5420
- id: flavourAxisId,
5421
- business_id: businessId,
6107
+ id: "axis_cake_flavour",
6108
+ business_id: BUSINESS_ID,
5422
6109
  product_id: "prod_birthday-cake",
5423
6110
  name: "Flavour",
5424
6111
  display_order: 1,
5425
6112
  affects_recipe: true,
5426
- values: flavourValues.map((v, i) => ({
6113
+ values: [
6114
+ { id: "axv_flavour_chocolate", name: "Dark Chocolate", display: 0 },
6115
+ { id: "axv_flavour_vanilla", name: "Vanilla Bean", display: 1 },
6116
+ { id: "axv_flavour_red_velvet", name: "Red Velvet", display: 2 }
6117
+ ].map((v) => ({
5427
6118
  id: v.id,
5428
- business_id: businessId,
5429
- axis_id: flavourAxisId,
6119
+ business_id: BUSINESS_ID,
6120
+ axis_id: "axis_cake_flavour",
5430
6121
  name: v.name,
5431
- display_order: i,
6122
+ display_order: v.display,
5432
6123
  created_at: ISO,
5433
6124
  updated_at: ISO
5434
6125
  })),
@@ -5437,40 +6128,42 @@ function seedDefault(registry) {
5437
6128
  };
5438
6129
  registry.variantAxes.put(sizeAxis.id, sizeAxis);
5439
6130
  registry.variantAxes.put(flavourAxis.id, flavourAxis);
5440
- const variantPrices = ["250.00", "350.00", "450.00"];
5441
- const variantIds = [];
5442
- let isFirst = true;
5443
- for (let s = 0; s < sizeValues.length; s++) {
5444
- for (let f = 0; f < flavourValues.length; f++) {
5445
- const id = `var_${sizeValues[s].id.replace("axv_", "")}_${flavourValues[f].id.replace("axv_", "")}`;
6131
+ const sizeAdj = { small: "250.00", medium: "350.00", large: "450.00" };
6132
+ const cakeVariantIds = [];
6133
+ let firstVariant = true;
6134
+ for (const sz of sizeAxis.values) {
6135
+ for (const fl of flavourAxis.values) {
6136
+ const sizeKey = sz.id.replace("axv_size_", "");
6137
+ const flavourKey = fl.id.replace("axv_flavour_", "");
6138
+ const id = `var_size_${sizeKey}_flavour_${flavourKey}`;
5446
6139
  const variant = {
5447
6140
  id,
5448
6141
  product_id: "prod_birthday-cake",
5449
- business_id: businessId,
5450
- name: `${sizeValues[s].name} \u2022 ${flavourValues[f].name}`,
5451
- sku: `BIRTHDAY-${sizeValues[s].id.split("_").pop()}-${flavourValues[f].id.split("_").pop()}`,
5452
- price_adjustment: variantPrices[s],
6142
+ business_id: BUSINESS_ID,
6143
+ name: `${sz.name} \u2022 ${fl.name}`,
6144
+ sku: `BIRTHDAY-${sizeKey.toUpperCase()}-${flavourKey.toUpperCase()}`,
6145
+ price_adjustment: sizeAdj[sizeKey] ?? "0.00",
5453
6146
  component_multiplier: "1.00",
5454
- is_default: isFirst,
6147
+ is_default: firstVariant,
5455
6148
  is_active: true,
5456
- axis_value_ids: [sizeValues[s].id, flavourValues[f].id],
6149
+ axis_value_ids: [sz.id, fl.id],
5457
6150
  created_at: ISO,
5458
6151
  updated_at: ISO
5459
6152
  };
5460
6153
  registry.variants.put(id, variant);
5461
- variantIds.push(id);
5462
- isFirst = false;
6154
+ cakeVariantIds.push(id);
6155
+ firstVariant = false;
5463
6156
  }
5464
6157
  }
5465
- const birthday = registry.products.get("prod_birthday-cake");
5466
- if (birthday) {
5467
- birthday.variant_ids = variantIds;
5468
- registry.products.put(birthday.id, birthday);
6158
+ const birthdayProduct = registry.products.get("prod_birthday-cake");
6159
+ if (birthdayProduct) {
6160
+ birthdayProduct.variant_ids = cakeVariantIds;
6161
+ registry.products.put(birthdayProduct.id, birthdayProduct);
5469
6162
  }
5470
6163
  const toppingsAddOn = {
5471
6164
  id: "addon_toppings",
5472
- business_id: businessId,
5473
- name: "Toppings",
6165
+ business_id: BUSINESS_ID,
6166
+ name: "Cake toppings",
5474
6167
  is_multiple_allowed: true,
5475
6168
  is_required: false,
5476
6169
  is_mutually_exclusive: false,
@@ -5479,15 +6172,14 @@ function seedDefault(registry) {
5479
6172
  created_at: ISO,
5480
6173
  updated_at: ISO,
5481
6174
  options: [
5482
- { 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 },
5483
- { 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 },
5484
- { 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 }
6175
+ { 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 },
6176
+ { 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 },
6177
+ { 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 }
5485
6178
  ]
5486
6179
  };
5487
- registry.addOns.put(toppingsAddOn.id, toppingsAddOn);
5488
6180
  const milkAddOn = {
5489
6181
  id: "addon_milk",
5490
- business_id: businessId,
6182
+ business_id: BUSINESS_ID,
5491
6183
  name: "Milk",
5492
6184
  is_multiple_allowed: false,
5493
6185
  is_required: false,
@@ -5497,17 +6189,17 @@ function seedDefault(registry) {
5497
6189
  created_at: ISO,
5498
6190
  updated_at: ISO,
5499
6191
  options: [
5500
- { 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 },
5501
- { 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 },
5502
- { 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 }
6192
+ { 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 },
6193
+ { 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 },
6194
+ { 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 }
5503
6195
  ]
5504
6196
  };
6197
+ registry.addOns.put(toppingsAddOn.id, toppingsAddOn);
5505
6198
  registry.addOns.put(milkAddOn.id, milkAddOn);
5506
6199
  const cakeProductIds = ["prod_chocolate-cake", "prod_red-velvet", "prod_carrot-cake", "prod_cheesecake", "prod_birthday-cake"];
5507
6200
  const drinkProductIds = ["prod_coffee", "prod_tea"];
5508
6201
  for (const pid of cakeProductIds) {
5509
- const link = { product_id: pid, add_on_id: toppingsAddOn.id };
5510
- registry.productAddOns.put(`${pid}:${toppingsAddOn.id}`, link);
6202
+ registry.productAddOns.put(`${pid}:${toppingsAddOn.id}`, { product_id: pid, add_on_id: toppingsAddOn.id });
5511
6203
  const p = registry.products.get(pid);
5512
6204
  if (p) {
5513
6205
  p.add_on_ids.push(toppingsAddOn.id);
@@ -5515,8 +6207,7 @@ function seedDefault(registry) {
5515
6207
  }
5516
6208
  }
5517
6209
  for (const pid of drinkProductIds) {
5518
- const link = { product_id: pid, add_on_id: milkAddOn.id };
5519
- registry.productAddOns.put(`${pid}:${milkAddOn.id}`, link);
6210
+ registry.productAddOns.put(`${pid}:${milkAddOn.id}`, { product_id: pid, add_on_id: milkAddOn.id });
5520
6211
  const p = registry.products.get(pid);
5521
6212
  if (p) {
5522
6213
  p.add_on_ids.push(milkAddOn.id);
@@ -5525,7 +6216,7 @@ function seedDefault(registry) {
5525
6216
  }
5526
6217
  const breakfastBundle = {
5527
6218
  id: "bnd_breakfast_box",
5528
- business_id: businessId,
6219
+ business_id: BUSINESS_ID,
5529
6220
  product_id: "prod_breakfast-box",
5530
6221
  name: "Breakfast Box",
5531
6222
  slug: "breakfast-box",
@@ -5537,29 +6228,9 @@ function seedDefault(registry) {
5537
6228
  updated_at: ISO
5538
6229
  };
5539
6230
  registry.bundles.put(breakfastBundle.id, breakfastBundle);
5540
- const breakfastProduct = {
5541
- id: "prod_breakfast-box",
5542
- business_id: businessId,
5543
- name: "Breakfast Box",
5544
- slug: "breakfast-box",
5545
- description: "Bundled breakfast for one.",
5546
- product_type: "bundle",
5547
- base_price: "30.00",
5548
- currency: "GHS",
5549
- image: "https://mock.cimplify.io/img/breakfast-box.jpg",
5550
- images: ["https://mock.cimplify.io/img/breakfast-box.jpg"],
5551
- is_available: true,
5552
- category_ids: [categoryIdsBySlug.get("pastries")],
5553
- collection_ids: [],
5554
- add_on_ids: [],
5555
- variant_ids: [],
5556
- created_at: ISO,
5557
- updated_at: ISO
5558
- };
5559
- registry.products.put(breakfastProduct.id, breakfastProduct);
5560
6231
  const pastryComposite = {
5561
6232
  id: "comp_pastry_box",
5562
- business_id: businessId,
6233
+ business_id: BUSINESS_ID,
5563
6234
  product_id: "prod_pastry-box",
5564
6235
  base_price: "60.00",
5565
6236
  pricing_mode: "additive",
@@ -5571,43 +6242,51 @@ function seedDefault(registry) {
5571
6242
  name: "Choose your pastries",
5572
6243
  min: 6,
5573
6244
  max: 12,
5574
- component_ids: ["prod_croissant", "prod_pain-au-chocolat", "prod_almond-danish", "prod_cinnamon-roll", "prod_brownie"]
6245
+ component_ids: [
6246
+ "prod_croissant",
6247
+ "prod_pain-au-chocolat",
6248
+ "prod_almond-danish",
6249
+ "prod_cinnamon-roll",
6250
+ "prod_brownie",
6251
+ "prod_banana-bread",
6252
+ "prod_macaron-box"
6253
+ ]
5575
6254
  }
5576
6255
  ],
5577
6256
  created_at: ISO,
5578
6257
  updated_at: ISO
5579
6258
  };
5580
6259
  registry.composites.put(pastryComposite.id, pastryComposite);
5581
- const pastryBoxProduct = {
5582
- id: "prod_pastry-box",
5583
- business_id: businessId,
5584
- name: "Build-Your-Own Pastry Box",
5585
- slug: "pastry-box",
5586
- description: "Pick 6 to 12 pastries \u2014 mix and match.",
5587
- product_type: "composite",
5588
- base_price: "60.00",
5589
- currency: "GHS",
5590
- image: "https://mock.cimplify.io/img/pastry-box.jpg",
5591
- images: ["https://mock.cimplify.io/img/pastry-box.jpg"],
5592
- is_available: true,
5593
- category_ids: [categoryIdsBySlug.get("pastries")],
5594
- collection_ids: [],
5595
- add_on_ids: [],
5596
- variant_ids: [],
5597
- created_at: ISO,
5598
- updated_at: ISO
6260
+ const tasting = {
6261
+ id: "svc_tasting",
6262
+ business_id: BUSINESS_ID,
6263
+ name: "Bakery Tasting Session",
6264
+ description: "Sample our pastry menu with our head baker.",
6265
+ duration_minutes: 60,
6266
+ price: "150.00",
6267
+ is_available: true
6268
+ };
6269
+ const consult = {
6270
+ id: "svc_custom_cake",
6271
+ business_id: BUSINESS_ID,
6272
+ name: "Custom Cake Consultation",
6273
+ description: "Design a custom cake for your event.",
6274
+ duration_minutes: 30,
6275
+ price: "0.00",
6276
+ is_available: true
5599
6277
  };
5600
- registry.products.put(pastryBoxProduct.id, pastryBoxProduct);
6278
+ registry.services.put(tasting.id, tasting);
6279
+ registry.services.put(consult.id, consult);
5601
6280
  const tags = [
5602
- { id: "tag_signature", business_id: businessId, name: "Signature", slug: "signature", color: "#d97706", sort_order: 0, usage_count: 5, created_at: ISO, updated_at: ISO },
5603
- { id: "tag_vegan", business_id: businessId, name: "Vegan", slug: "vegan", color: "#16a34a", sort_order: 1, usage_count: 3, created_at: ISO, updated_at: ISO },
5604
- { 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 },
5605
- { id: "tag_seasonal", business_id: businessId, name: "Seasonal", slug: "seasonal", color: "#dc2626", sort_order: 3, usage_count: 4, created_at: ISO, updated_at: ISO }
6281
+ { 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 },
6282
+ { 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 },
6283
+ { 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 },
6284
+ { 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 }
5606
6285
  ];
5607
6286
  for (const t of tags) registry.tags.put(t.id, t);
5608
6287
  const baked = {
5609
6288
  id: "tax_baked_goods",
5610
- business_id: businessId,
6289
+ business_id: BUSINESS_ID,
5611
6290
  name: "Baked Goods",
5612
6291
  slug: "baked-goods",
5613
6292
  path: ["Food", "Baked Goods"],
@@ -5617,7 +6296,7 @@ function seedDefault(registry) {
5617
6296
  };
5618
6297
  const beverages = {
5619
6298
  id: "tax_beverages",
5620
- business_id: businessId,
6299
+ business_id: BUSINESS_ID,
5621
6300
  name: "Beverages",
5622
6301
  slug: "beverages",
5623
6302
  path: ["Food", "Beverages"],
@@ -5630,13 +6309,13 @@ function seedDefault(registry) {
5630
6309
  const attrs = [
5631
6310
  {
5632
6311
  id: "attr_allergens",
5633
- business_id: businessId,
6312
+ business_id: BUSINESS_ID,
5634
6313
  namespace: "core",
5635
6314
  name: "Allergens",
5636
6315
  slug: "allergens",
5637
6316
  description: "Common allergens present in this item.",
5638
6317
  attribute_type: "multi_select",
5639
- options: ["wheat", "eggs", "dairy", "nuts", "soy"],
6318
+ options: ["wheat", "gluten", "eggs", "dairy", "nuts", "soy", "sesame"],
5640
6319
  is_required: false,
5641
6320
  is_filterable: true,
5642
6321
  visibility: "public",
@@ -5647,7 +6326,7 @@ function seedDefault(registry) {
5647
6326
  },
5648
6327
  {
5649
6328
  id: "attr_shelf_life",
5650
- business_id: businessId,
6329
+ business_id: BUSINESS_ID,
5651
6330
  namespace: "core",
5652
6331
  name: "Shelf life",
5653
6332
  slug: "shelf-life",
@@ -5662,7 +6341,7 @@ function seedDefault(registry) {
5662
6341
  },
5663
6342
  {
5664
6343
  id: "attr_caffeine",
5665
- business_id: businessId,
6344
+ business_id: BUSINESS_ID,
5666
6345
  namespace: "core",
5667
6346
  name: "Caffeine",
5668
6347
  slug: "caffeine",
@@ -5677,7 +6356,7 @@ function seedDefault(registry) {
5677
6356
  },
5678
6357
  {
5679
6358
  id: "attr_seasonal",
5680
- business_id: businessId,
6359
+ business_id: BUSINESS_ID,
5681
6360
  namespace: "core",
5682
6361
  name: "Seasonal",
5683
6362
  slug: "seasonal",
@@ -5695,10 +6374,10 @@ function seedDefault(registry) {
5695
6374
  const articles = [
5696
6375
  {
5697
6376
  id: "kb_delivery",
5698
- business_id: businessId,
5699
- title: "Delivery & Pickup",
6377
+ business_id: BUSINESS_ID,
6378
+ title: "Delivery & pickup",
5700
6379
  slug: "delivery-pickup",
5701
- content: "Same-day delivery within Accra for orders placed before 2pm. Pickup available 8am\u20138pm.",
6380
+ content: "Same-day delivery within Greater Accra for orders placed before 2pm. Pickup available 8am\u20138pm at our East Legon location.",
5702
6381
  category: "logistics",
5703
6382
  tags: ["delivery", "pickup"],
5704
6383
  created_at: ISO,
@@ -5706,10 +6385,10 @@ function seedDefault(registry) {
5706
6385
  },
5707
6386
  {
5708
6387
  id: "kb_custom_cakes",
5709
- business_id: businessId,
5710
- title: "Ordering custom cakes",
6388
+ business_id: BUSINESS_ID,
6389
+ title: "Custom cakes & celebrations",
5711
6390
  slug: "custom-cakes",
5712
- content: "Custom cakes require 48 hours notice. Book a consultation through the bookings page.",
6391
+ content: "Custom cakes require 48 hours notice. Book a consultation through the bookings page to walk through flavours, sizes, and decoration.",
5713
6392
  category: "products",
5714
6393
  tags: ["cakes", "custom"],
5715
6394
  created_at: ISO,
@@ -5717,10 +6396,10 @@ function seedDefault(registry) {
5717
6396
  },
5718
6397
  {
5719
6398
  id: "kb_allergens",
5720
- business_id: businessId,
5721
- title: "Allergens",
6399
+ business_id: BUSINESS_ID,
6400
+ title: "Allergen information",
5722
6401
  slug: "allergens",
5723
- content: "Our kitchen handles wheat, dairy, eggs, and nuts. Cross-contamination is possible.",
6402
+ content: "Our kitchen handles wheat, dairy, eggs, and nuts. Cross-contamination is possible \u2014 let us know about severe allergies before ordering.",
5724
6403
  category: "products",
5725
6404
  tags: ["allergens", "safety"],
5726
6405
  created_at: ISO,
@@ -5728,7 +6407,7 @@ function seedDefault(registry) {
5728
6407
  }
5729
6408
  ];
5730
6409
  for (const a of articles) registry.knowledgeArticles.put(a.id, a);
5731
- return { businessId };
6410
+ return { businessId: BUSINESS_ID };
5732
6411
  }
5733
6412
 
5734
6413
  // src/mock/seeds/empty.ts
@@ -5774,7 +6453,7 @@ function seedReesa(registry) {
5774
6453
  default_address: "5 Allen Avenue, Ikeja, Lagos",
5775
6454
  default_offers_table_service: false,
5776
6455
  default_accepts_online_orders: true,
5777
- image: "https://mock.cimplify.io/img/reesa-logo.png",
6456
+ image: "/img/reesa-logo.png",
5778
6457
  status: "active",
5779
6458
  created_at: ISO3,
5780
6459
  updated_at: ISO3,
@@ -5830,8 +6509,8 @@ function seedReesa(registry) {
5830
6509
  product_type: "product",
5831
6510
  base_price: p.price,
5832
6511
  currency: "NGN",
5833
- image: `https://mock.cimplify.io/img/reesa/${p.slug}.jpg`,
5834
- images: [`https://mock.cimplify.io/img/reesa/${p.slug}.jpg`],
6512
+ image: `/img/reesa/${p.slug}.jpg`,
6513
+ images: [`/img/reesa/${p.slug}.jpg`],
5835
6514
  is_available: true,
5836
6515
  category_ids: [catId],
5837
6516
  collection_ids: [],
@@ -6054,8 +6733,8 @@ function seedReesa(registry) {
6054
6733
  product_type: "bundle",
6055
6734
  base_price: "55000.00",
6056
6735
  currency: "NGN",
6057
- image: "https://mock.cimplify.io/img/reesa/owambe.jpg",
6058
- images: ["https://mock.cimplify.io/img/reesa/owambe.jpg"],
6736
+ image: "/img/reesa/owambe.jpg",
6737
+ images: ["/img/reesa/owambe.jpg"],
6059
6738
  is_available: true,
6060
6739
  category_ids: [catIdsBySlug.get("apparel")],
6061
6740
  collection_ids: [],
@@ -6186,7 +6865,7 @@ function seedRestaurant(registry) {
6186
6865
  default_address: "8 Ring Road, East Legon, Accra",
6187
6866
  default_offers_table_service: true,
6188
6867
  default_accepts_online_orders: true,
6189
- image: "https://mock.cimplify.io/img/mamas-logo.png",
6868
+ image: "/img/mamas-logo.png",
6190
6869
  status: "active",
6191
6870
  created_at: ISO4,
6192
6871
  updated_at: ISO4,
@@ -6257,8 +6936,8 @@ function seedRestaurant(registry) {
6257
6936
  product_type: "product",
6258
6937
  base_price: p.price,
6259
6938
  currency: "GHS",
6260
- image: `https://mock.cimplify.io/img/mamas/${p.slug}.jpg`,
6261
- images: [`https://mock.cimplify.io/img/mamas/${p.slug}.jpg`],
6939
+ image: `/img/mamas/${p.slug}.jpg`,
6940
+ images: [`/img/mamas/${p.slug}.jpg`],
6262
6941
  is_available: true,
6263
6942
  category_ids: [catId],
6264
6943
  collection_ids: [],
@@ -6496,8 +7175,8 @@ function seedRestaurant(registry) {
6496
7175
  product_type: "bundle",
6497
7176
  base_price: "180.00",
6498
7177
  currency: "GHS",
6499
- image: "https://mock.cimplify.io/img/mamas/feast.jpg",
6500
- images: ["https://mock.cimplify.io/img/mamas/feast.jpg"],
7178
+ image: "/img/mamas/feast.jpg",
7179
+ images: ["/img/mamas/feast.jpg"],
6501
7180
  is_available: true,
6502
7181
  category_ids: [catIds.get("mains")],
6503
7182
  collection_ids: [],
@@ -6532,8 +7211,8 @@ function seedRestaurant(registry) {
6532
7211
  product_type: "composite",
6533
7212
  base_price: "0.00",
6534
7213
  currency: "GHS",
6535
- image: "https://mock.cimplify.io/img/mamas/byo.jpg",
6536
- images: ["https://mock.cimplify.io/img/mamas/byo.jpg"],
7214
+ image: "/img/mamas/byo.jpg",
7215
+ images: ["/img/mamas/byo.jpg"],
6537
7216
  is_available: true,
6538
7217
  category_ids: [catIds.get("mains")],
6539
7218
  collection_ids: [],
@@ -6645,7 +7324,7 @@ function seedRetail(registry) {
6645
7324
  default_address: "Atomic Junction, Accra",
6646
7325
  default_offers_table_service: false,
6647
7326
  default_accepts_online_orders: true,
6648
- image: "https://mock.cimplify.io/img/currents-logo.png",
7327
+ image: "/img/currents-logo.png",
6649
7328
  status: "active",
6650
7329
  created_at: ISO5,
6651
7330
  updated_at: ISO5,
@@ -6715,8 +7394,8 @@ function seedRetail(registry) {
6715
7394
  product_type: "product",
6716
7395
  base_price: p.price,
6717
7396
  currency: "GHS",
6718
- image: `https://mock.cimplify.io/img/currents/${p.slug}.jpg`,
6719
- images: [`https://mock.cimplify.io/img/currents/${p.slug}.jpg`],
7397
+ image: `/img/currents/${p.slug}.jpg`,
7398
+ images: [`/img/currents/${p.slug}.jpg`],
6720
7399
  is_available: true,
6721
7400
  category_ids: [catId],
6722
7401
  collection_ids: [],
@@ -6979,8 +7658,8 @@ function seedRetail(registry) {
6979
7658
  product_type: "bundle",
6980
7659
  base_price: "11910.00",
6981
7660
  currency: "GHS",
6982
- image: "https://mock.cimplify.io/img/currents/wfh.jpg",
6983
- images: ["https://mock.cimplify.io/img/currents/wfh.jpg"],
7661
+ image: "/img/currents/wfh.jpg",
7662
+ images: ["/img/currents/wfh.jpg"],
6984
7663
  is_available: true,
6985
7664
  category_ids: [catIds.get("laptops")],
6986
7665
  collection_ids: [],
@@ -7013,8 +7692,8 @@ function seedRetail(registry) {
7013
7692
  product_type: "bundle",
7014
7693
  base_price: "4200.00",
7015
7694
  currency: "GHS",
7016
- image: "https://mock.cimplify.io/img/currents/game-pack.jpg",
7017
- images: ["https://mock.cimplify.io/img/currents/game-pack.jpg"],
7695
+ image: "/img/currents/game-pack.jpg",
7696
+ images: ["/img/currents/game-pack.jpg"],
7018
7697
  is_available: true,
7019
7698
  category_ids: [catIds.get("gaming")],
7020
7699
  collection_ids: [],
@@ -7117,7 +7796,7 @@ function seedServices(registry) {
7117
7796
  default_address: "12 Cantonments Road, Accra",
7118
7797
  default_offers_table_service: false,
7119
7798
  default_accepts_online_orders: true,
7120
- image: "https://mock.cimplify.io/img/serene-logo.png",
7799
+ image: "/img/serene-logo.png",
7121
7800
  status: "active",
7122
7801
  created_at: ISO6,
7123
7802
  updated_at: ISO6,
@@ -7185,8 +7864,8 @@ function seedServices(registry) {
7185
7864
  product_type: "service",
7186
7865
  base_price: s.price,
7187
7866
  currency: "GHS",
7188
- image: `https://mock.cimplify.io/img/serene/${s.slug}.jpg`,
7189
- images: [`https://mock.cimplify.io/img/serene/${s.slug}.jpg`],
7867
+ image: `/img/serene/${s.slug}.jpg`,
7868
+ images: [`/img/serene/${s.slug}.jpg`],
7190
7869
  is_available: true,
7191
7870
  category_ids: [catId],
7192
7871
  collection_ids: [],
@@ -7322,8 +8001,8 @@ function seedServices(registry) {
7322
8001
  product_type: "bundle",
7323
8002
  base_price: "480.00",
7324
8003
  currency: "GHS",
7325
- image: "https://mock.cimplify.io/img/serene/spa-day.jpg",
7326
- images: ["https://mock.cimplify.io/img/serene/spa-day.jpg"],
8004
+ image: "/img/serene/spa-day.jpg",
8005
+ images: ["/img/serene/spa-day.jpg"],
7327
8006
  is_available: true,
7328
8007
  category_ids: [catIds.get("wellness")],
7329
8008
  collection_ids: [],
@@ -7356,8 +8035,8 @@ function seedServices(registry) {
7356
8035
  product_type: "bundle",
7357
8036
  base_price: "850.50",
7358
8037
  currency: "GHS",
7359
- image: "https://mock.cimplify.io/img/serene/couples.jpg",
7360
- images: ["https://mock.cimplify.io/img/serene/couples.jpg"],
8038
+ image: "/img/serene/couples.jpg",
8039
+ images: ["/img/serene/couples.jpg"],
7361
8040
  is_available: true,
7362
8041
  category_ids: [catIds.get("wellness")],
7363
8042
  collection_ids: [],
@@ -7392,8 +8071,8 @@ function seedServices(registry) {
7392
8071
  product_type: "composite",
7393
8072
  base_price: "0.00",
7394
8073
  currency: "GHS",
7395
- image: "https://mock.cimplify.io/img/serene/custom.jpg",
7396
- images: ["https://mock.cimplify.io/img/serene/custom.jpg"],
8074
+ image: "/img/serene/custom.jpg",
8075
+ images: ["/img/serene/custom.jpg"],
7397
8076
  is_available: true,
7398
8077
  category_ids: [catIds.get("wellness")],
7399
8078
  collection_ids: [],
@@ -7496,7 +8175,7 @@ function seedGrocery(registry) {
7496
8175
  default_address: "Spintex Road, Accra",
7497
8176
  default_offers_table_service: false,
7498
8177
  default_accepts_online_orders: true,
7499
- image: "https://mock.cimplify.io/img/freshmart-logo.png",
8178
+ image: "/img/freshmart-logo.png",
7500
8179
  status: "active",
7501
8180
  created_at: ISO7,
7502
8181
  updated_at: ISO7,
@@ -7571,8 +8250,8 @@ function seedGrocery(registry) {
7571
8250
  product_type: "product",
7572
8251
  base_price: p.price,
7573
8252
  currency: "GHS",
7574
- image: `https://mock.cimplify.io/img/freshmart/${p.slug}.jpg`,
7575
- images: [`https://mock.cimplify.io/img/freshmart/${p.slug}.jpg`],
8253
+ image: `/img/freshmart/${p.slug}.jpg`,
8254
+ images: [`/img/freshmart/${p.slug}.jpg`],
7576
8255
  is_available: true,
7577
8256
  category_ids: [catId],
7578
8257
  collection_ids: [],
@@ -7655,8 +8334,8 @@ function seedGrocery(registry) {
7655
8334
  product_type: "bundle",
7656
8335
  base_price: "199.50",
7657
8336
  currency: "GHS",
7658
- image: "https://mock.cimplify.io/img/freshmart/essentials.jpg",
7659
- images: ["https://mock.cimplify.io/img/freshmart/essentials.jpg"],
8337
+ image: "/img/freshmart/essentials.jpg",
8338
+ images: ["/img/freshmart/essentials.jpg"],
7660
8339
  is_available: true,
7661
8340
  category_ids: [catIds.get("pantry")],
7662
8341
  collection_ids: [],
@@ -7831,43 +8510,47 @@ function createDeps(options = {}) {
7831
8510
  }
7832
8511
 
7833
8512
  // src/mock/server.ts
8513
+ var PACKAGE_NAME = "@cimplify/sdk/mock";
8514
+ function corsMiddleware(allow) {
8515
+ return async (c, next) => {
8516
+ const origin = c.req.header("origin") ?? "";
8517
+ const allowOrigin = allow === "*" ? "*" : allow.includes(origin) ? origin : allow[0] ?? "*";
8518
+ const headers = {
8519
+ "Access-Control-Allow-Origin": allowOrigin,
8520
+ "Access-Control-Allow-Credentials": "true",
8521
+ "Access-Control-Allow-Headers": c.req.header("access-control-request-headers") ?? "content-type,authorization,x-session-token,idempotency-key",
8522
+ "Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
8523
+ "Access-Control-Expose-Headers": "x-session-token",
8524
+ "Access-Control-Max-Age": "86400",
8525
+ Vary: "Origin"
8526
+ };
8527
+ if (c.req.method === "OPTIONS") {
8528
+ return new Response(null, { status: 204, headers });
8529
+ }
8530
+ for (const [k, v] of Object.entries(headers)) c.header(k, v);
8531
+ await next();
8532
+ };
8533
+ }
7834
8534
  function createMockApp(options = {}) {
7835
8535
  const deps = createDeps(options);
7836
8536
  const app = new Hono2();
7837
- const corsOrigins = options.cors === "*" ? "*" : options.cors;
7838
- if (corsOrigins) {
7839
- app.use("*", async (c, next) => {
7840
- const origin = c.req.header("origin") ?? "";
7841
- const allow = corsOrigins === "*" ? "*" : corsOrigins.includes(origin) ? origin : corsOrigins[0] ?? "*";
7842
- const headers = {
7843
- "Access-Control-Allow-Origin": allow,
7844
- "Access-Control-Allow-Credentials": "true",
7845
- "Access-Control-Allow-Headers": c.req.header("access-control-request-headers") ?? "content-type,authorization,x-session-token,idempotency-key",
7846
- "Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
7847
- "Access-Control-Expose-Headers": "x-session-token",
7848
- "Access-Control-Max-Age": "86400",
7849
- Vary: "Origin"
7850
- };
7851
- if (c.req.method === "OPTIONS") {
7852
- return new Response(null, { status: 204, headers });
7853
- }
7854
- for (const [k, v] of Object.entries(headers)) c.header(k, v);
7855
- await next();
7856
- });
7857
- }
8537
+ const cors = options.cors ?? "*";
8538
+ if (cors) app.use("*", corsMiddleware(cors));
7858
8539
  app.get(
7859
8540
  "/",
7860
8541
  (c) => c.json({
7861
- name: "@cimplify/sdk/mock",
7862
- version: "0.36.0",
8542
+ name: PACKAGE_NAME,
7863
8543
  docs: "https://github.com/cimplify/cimplify",
7864
8544
  api: "/api/v1",
7865
8545
  admin: "/_mock",
7866
8546
  elements: "/elements",
7867
- seed_business_id: deps.defaultBusinessId
8547
+ images: "/img/<slug>",
8548
+ seed_business_id: deps.defaultBusinessId,
8549
+ time: deps.clock.iso()
7868
8550
  })
7869
8551
  );
7870
8552
  app.get("/healthz", (c) => c.json({ ok: true, time: deps.clock.iso() }));
8553
+ mountImageRoutes(app);
7871
8554
  app.route("/api/v1", buildV1(deps));
7872
8555
  app.route("/_mock", buildAdmin(deps));
7873
8556
  app.route("/elements", buildElements(deps));
@@ -7875,7 +8558,11 @@ function createMockApp(options = {}) {
7875
8558
  (c) => c.json(
7876
8559
  {
7877
8560
  success: false,
7878
- error: { code: "ROUTE_NOT_REGISTERED", message: `Route ${c.req.method} ${c.req.path} not handled by mock`, retryable: false },
8561
+ error: {
8562
+ code: "ROUTE_NOT_REGISTERED",
8563
+ message: `Route ${c.req.method} ${c.req.path} not handled by mock`,
8564
+ retryable: false
8565
+ },
7879
8566
  hint: "See /api/v1 for the SDK surface and /_mock for the admin surface."
7880
8567
  },
7881
8568
  501