@cimplify/sdk 0.38.2 → 0.38.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/dispatcher.mjs +1 -1
  2. package/dist/mock/cli.mjs +956 -436
  3. package/dist/mock/library.d.mts +2 -2
  4. package/dist/mock/library.d.ts +2 -2
  5. package/dist/mock/library.js +956 -436
  6. package/dist/mock/library.mjs +956 -436
  7. package/dist/mock/msw.d.mts +1 -1
  8. package/dist/mock/msw.d.ts +1 -1
  9. package/dist/mock/msw.js +956 -436
  10. package/dist/mock/msw.mjs +956 -436
  11. package/dist/{server-B8TeE2OD.d.mts → server-BNy6Ri-4.d.mts} +18 -0
  12. package/dist/{server-B8TeE2OD.d.ts → server-BNy6Ri-4.d.ts} +18 -0
  13. package/dist/styles.css +1 -1
  14. package/package.json +1 -1
  15. package/templates/storefront/.env.example +11 -2
  16. package/templates/storefront/README.md +35 -18
  17. package/templates/storefront/app/cart/page.tsx +9 -0
  18. package/templates/storefront/app/categories/[slug]/page.tsx +80 -0
  19. package/templates/storefront/app/checkout/page.tsx +17 -0
  20. package/templates/storefront/app/collections/[slug]/page.tsx +80 -0
  21. package/templates/storefront/app/globals.css +47 -0
  22. package/templates/storefront/app/layout.tsx +45 -0
  23. package/templates/storefront/app/orders/[id]/page.tsx +21 -0
  24. package/templates/storefront/app/page.tsx +65 -0
  25. package/templates/storefront/app/shop/page.tsx +46 -0
  26. package/templates/storefront/components/category-grid.tsx +28 -0
  27. package/templates/storefront/components/collection-strip.tsx +45 -0
  28. package/templates/storefront/components/footer.tsx +19 -0
  29. package/templates/storefront/components/header.tsx +41 -0
  30. package/templates/storefront/components/hero.tsx +25 -0
  31. package/templates/storefront/components/product-modal.tsx +105 -0
  32. package/templates/storefront/components/providers.tsx +31 -0
  33. package/templates/storefront/components/store-product-card.tsx +48 -0
  34. package/templates/storefront/lib/cart.ts +27 -0
  35. package/templates/storefront/next.config.ts +36 -0
  36. package/templates/storefront/package.json +13 -14
  37. package/templates/storefront/postcss.config.mjs +7 -0
  38. package/templates/storefront/tsconfig.json +15 -11
  39. package/templates/storefront/index.html +0 -12
  40. package/templates/storefront/src/App.tsx +0 -118
  41. package/templates/storefront/src/main.tsx +0 -28
  42. package/templates/storefront/src/styles.css +0 -65
  43. package/templates/storefront/vite.config.ts +0 -8
package/dist/mock/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,51 +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 defaultPriceInfo = (price, currency) => ({
4130
+ // src/mock/domain/catalogue/price-info.ts
4131
+ function defaultPriceInfo(price, currency) {
4132
+ return {
3963
4133
  base_price: price,
3964
4134
  final_price: price,
4135
+ pre_tax_price: price,
3965
4136
  markup_percentage: "0.00",
3966
4137
  markup_amount: "0.00",
3967
4138
  markup_discount_percentage: "0.00",
3968
4139
  markup_discount_amount: "0.00",
3969
- currency
3970
- });
3971
- const toApiProduct = (p) => ({
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) ───
3972
4184
  id: p.id,
3973
4185
  business_id: p.business_id,
4186
+ category_id: p.category_ids[0],
3974
4187
  name: p.name,
3975
4188
  slug: p.slug,
3976
4189
  description: p.description ?? "",
3977
- product_type: p.product_type,
3978
- base_price: p.base_price,
4190
+ image_url: p.image,
4191
+ images: p.images,
3979
4192
  default_price: p.base_price,
3980
- default_price_info: defaultPriceInfo(p.base_price, p.currency),
3981
- price_info: defaultPriceInfo(p.base_price, p.currency),
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) ───
4212
+ base_price: p.base_price,
4213
+ default_price_info: priceInfo,
4214
+ price_info: priceInfo,
3982
4215
  currency: p.currency,
4216
+ // ─── Compatibility aliases (older callers) ───
3983
4217
  image: p.image,
3984
- image_url: p.image,
3985
- images: p.images,
4218
+ product_type: p.product_type,
3986
4219
  is_available: p.is_available,
3987
- category_id: p.category_ids[0],
3988
4220
  category_ids: p.category_ids,
3989
4221
  collection_ids: p.collection_ids,
3990
4222
  add_on_ids: p.add_on_ids,
3991
- metadata: p.metadata ?? {},
3992
- created_at: p.created_at,
3993
- updated_at: p.updated_at,
4223
+ variant_ids: p.variant_ids,
3994
4224
  custom_attributes: [],
3995
4225
  properties: [],
3996
4226
  has_variants: p.variant_ids.length > 0,
3997
- type: p.product_type,
3998
- render_hint: "general"
3999
- });
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);
4000
4347
  const variantsForProduct = (productId) => registry.variants.list((v) => v.product_id === productId);
4001
4348
  const axesForProduct = (productId) => registry.variantAxes.list((a) => a.product_id === productId);
4002
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];
4003
4373
  return {
4004
4374
  snapshot(businessId) {
4005
4375
  const products = productsForBusiness(businessId);
@@ -4007,7 +4377,7 @@ function createCatalogueService(opts) {
4007
4377
  const addOns = registry.addOns.list((a) => a.business_id === businessId);
4008
4378
  return {
4009
4379
  categories,
4010
- products: products.map(toApiProduct),
4380
+ products: products.map(apiProduct),
4011
4381
  add_ons: addOns,
4012
4382
  is_complete: true,
4013
4383
  total_available: products.length
@@ -4022,7 +4392,7 @@ function createCatalogueService(opts) {
4022
4392
  const start = (page - 1) * limit;
4023
4393
  const slice = items.slice(start, start + limit);
4024
4394
  return {
4025
- items: slice.map(toApiProduct),
4395
+ items: slice.map(apiProduct),
4026
4396
  is_complete: start + limit >= items.length,
4027
4397
  total_available: items.length,
4028
4398
  pagination: {
@@ -4035,9 +4405,9 @@ function createCatalogueService(opts) {
4035
4405
  };
4036
4406
  },
4037
4407
  getProduct(productId) {
4038
- const p = registry.products.get(productId);
4408
+ const p = resolveProduct(productId);
4039
4409
  if (!p) throw notFound(`Product ${productId} not found`);
4040
- return toApiProduct(p);
4410
+ return apiProduct(p);
4041
4411
  },
4042
4412
  getProductVariants(productId) {
4043
4413
  return variantsForProduct(productId);
@@ -4047,7 +4417,9 @@ function createCatalogueService(opts) {
4047
4417
  },
4048
4418
  getProductVariantById(productId, variantId) {
4049
4419
  const v = registry.variants.get(variantId);
4050
- 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
+ }
4051
4423
  return v;
4052
4424
  },
4053
4425
  findProductVariant(productId, properties) {
@@ -4077,13 +4449,13 @@ function createCatalogueService(opts) {
4077
4449
  return registry.categories.list((c) => c.business_id === businessId);
4078
4450
  },
4079
4451
  getCategory(id) {
4080
- const c = registry.categories.get(id);
4452
+ const c = registry.categories.get(id) ?? registry.categories.list((x) => x.slug === id)[0];
4081
4453
  if (!c) throw notFound(`Category ${id} not found`);
4082
4454
  return c;
4083
4455
  },
4084
4456
  getCategoryProducts(id) {
4085
4457
  const c = this.getCategory(id);
4086
- 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);
4087
4459
  },
4088
4460
  getCategoryAttributes(id) {
4089
4461
  const c = registry.categories.get(id);
@@ -4096,13 +4468,13 @@ function createCatalogueService(opts) {
4096
4468
  return registry.collections.list((c) => c.business_id === businessId);
4097
4469
  },
4098
4470
  getCollection(id) {
4099
- const c = registry.collections.get(id);
4471
+ const c = registry.collections.get(id) ?? registry.collections.list((x) => x.slug === id)[0];
4100
4472
  if (!c) throw notFound(`Collection ${id} not found`);
4101
4473
  return c;
4102
4474
  },
4103
4475
  getCollectionProducts(id) {
4104
4476
  const c = this.getCollection(id);
4105
- 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);
4106
4478
  },
4107
4479
  getCollectionAttributes(id) {
4108
4480
  const c = registry.collections.get(id);
@@ -4115,7 +4487,7 @@ function createCatalogueService(opts) {
4115
4487
  return registry.bundles.list((b) => b.business_id === businessId);
4116
4488
  },
4117
4489
  getBundle(id) {
4118
- 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];
4119
4491
  if (!b) throw notFound(`Bundle ${id} not found`);
4120
4492
  return b;
4121
4493
  },
@@ -4123,12 +4495,16 @@ function createCatalogueService(opts) {
4123
4495
  return registry.composites.list((c) => c.business_id === businessId);
4124
4496
  },
4125
4497
  getComposite(id) {
4126
- const c = registry.composites.get(id);
4498
+ const c = registry.composites.get(id) ?? registry.composites.list((x) => x.product_id === id)[0];
4127
4499
  if (!c) throw notFound(`Composite ${id} not found`);
4128
4500
  return c;
4129
4501
  },
4130
4502
  getCompositeByProduct(productId) {
4131
- 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;
4132
4508
  },
4133
4509
  calculateCompositePrice(id, body) {
4134
4510
  const c = registry.composites.get(id);
@@ -4136,14 +4512,20 @@ function createCatalogueService(opts) {
4136
4512
  const sel = body.selections ?? [];
4137
4513
  let total = c.base_price;
4138
4514
  for (const s of sel) total = add(total, money(parseFloat(s.unit_price ?? "0") * (s.quantity ?? 1)));
4139
- 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
+ };
4140
4520
  },
4141
4521
  listTaxonomies(businessId) {
4142
4522
  return registry.taxonomies.list((t) => t.business_id === businessId);
4143
4523
  },
4144
4524
  searchTaxonomies(businessId, q) {
4145
4525
  const term = q.toLowerCase();
4146
- 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
+ );
4147
4529
  },
4148
4530
  getTaxonomy(id) {
4149
4531
  const t = registry.taxonomies.get(id);
@@ -4177,7 +4559,11 @@ function createCatalogueService(opts) {
4177
4559
  listPropertyFacets(businessId) {
4178
4560
  const products = productsForBusiness(businessId);
4179
4561
  const byCategory = /* @__PURE__ */ new Map();
4180
- 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
+ }
4181
4567
  return [
4182
4568
  {
4183
4569
  name: "Category",
@@ -4196,12 +4582,6 @@ function createCatalogueService(opts) {
4196
4582
  listOnSale() {
4197
4583
  return [];
4198
4584
  },
4199
- validateDiscount(code) {
4200
- if (code === "WELCOME10") {
4201
- return { valid: true, discount_id: "disc_welcome10", discount_value: "10.00", discount_type: "percentage" };
4202
- }
4203
- return { valid: false, reason: "Code not recognized" };
4204
- },
4205
4585
  getProductDeals() {
4206
4586
  return [];
4207
4587
  },
@@ -4211,15 +4591,33 @@ function createCatalogueService(opts) {
4211
4591
  getCollectionDeals() {
4212
4592
  return [];
4213
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
+ },
4214
4600
  createQuote(businessId, body) {
4215
4601
  const b = body;
4216
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);
4217
4608
  const quote = {
4218
4609
  id,
4610
+ quote_id: id,
4219
4611
  business_id: businessId,
4220
- product_id: b.product_id ?? "",
4612
+ product_id: product?.id ?? productId,
4221
4613
  status: "fresh",
4222
- 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 },
4223
4621
  expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString(),
4224
4622
  created_at: clock.iso()
4225
4623
  };
@@ -4235,7 +4633,11 @@ function createCatalogueService(opts) {
4235
4633
  refreshQuote(id) {
4236
4634
  const q = registry.quotes.get(id);
4237
4635
  if (!q) throw notFound(`Quote ${id} not found`);
4238
- 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
+ };
4239
4641
  registry.quotes.put(id, refreshed);
4240
4642
  return refreshed;
4241
4643
  }
@@ -5260,67 +5662,82 @@ function createLiteService(opts) {
5260
5662
 
5261
5663
  // src/mock/seeds/default.ts
5262
5664
  var ISO = "2026-01-01T00:00:00.000Z";
5263
- var PRODUCTS = [
5264
- { name: "Sourdough Loaf", slug: "sourdough-loaf", price: "25.00", category: "breads", description: "Artisanal 24-hour fermented sourdough.", image: "/img/sourdough.jpg" },
5265
- { name: "Whole Wheat Bread", slug: "whole-wheat", price: "20.00", category: "breads", description: "Hearty whole wheat with seeds.", image: "/img/wheat.jpg" },
5266
- { name: "Baguette", slug: "baguette", price: "15.00", category: "breads", description: "Crispy crust, soft inside.", image: "/img/baguette.jpg" },
5267
- { name: "Focaccia", slug: "focaccia", price: "30.00", category: "breads", description: "Olive oil and rosemary focaccia.", image: "/img/focaccia.jpg" },
5268
- { name: "Croissant", slug: "croissant", price: "12.00", category: "pastries", description: "Buttery French croissant.", image: "/img/croissant.jpg" },
5269
- { name: "Pain au Chocolat", slug: "pain-au-chocolat", price: "14.00", category: "pastries", description: "Croissant with dark chocolate.", image: "/img/pac.jpg" },
5270
- { name: "Almond Danish", slug: "almond-danish", price: "16.00", category: "pastries", description: "Flaky pastry with almond cream.", image: "/img/danish.jpg" },
5271
- { name: "Cinnamon Roll", slug: "cinnamon-roll", price: "13.00", category: "pastries", description: "Soft roll with cinnamon glaze.", image: "/img/cinnamon.jpg" },
5272
- { name: "Chocolate Cake (Slice)", slug: "chocolate-cake", price: "22.00", category: "cakes", description: "Rich dark chocolate cake.", image: "/img/choc-cake.jpg" },
5273
- { name: "Red Velvet (Slice)", slug: "red-velvet", price: "22.00", category: "cakes", description: "Classic red velvet with cream cheese.", image: "/img/red-velvet.jpg" },
5274
- { name: "Carrot Cake (Slice)", slug: "carrot-cake", price: "20.00", category: "cakes", description: "Spiced carrot cake with walnut.", image: "/img/carrot.jpg" },
5275
- { name: "Cheesecake (Slice)", slug: "cheesecake", price: "24.00", category: "cakes", description: "New York style cheesecake.", image: "/img/cheesecake.jpg" },
5276
- { name: "Coffee", slug: "coffee", price: "10.00", category: "drinks", description: "Locally roasted Arabica.", image: "/img/coffee.jpg" },
5277
- { name: "Tea", slug: "tea", price: "8.00", category: "drinks", description: "Black, green, or herbal.", image: "/img/tea.jpg" },
5278
- { name: "Hibiscus Tea (Sobolo)", slug: "sobolo", price: "12.00", category: "drinks", description: "Iced hibiscus drink.", image: "/img/sobolo.jpg" },
5279
- { name: "Fresh Orange Juice", slug: "orange-juice", price: "15.00", category: "drinks", description: "Cold-pressed orange juice.", image: "/img/oj.jpg" },
5280
- { name: "Banana Bread", slug: "banana-bread", price: "18.00", category: "breads", description: "Moist banana loaf.", image: "/img/banana.jpg" },
5281
- { name: "Brownie", slug: "brownie", price: "12.00", category: "pastries", description: "Fudgy chocolate brownie.", image: "/img/brownie.jpg" },
5282
- { name: "Macaron Box (6)", slug: "macaron-box", price: "60.00", category: "pastries", description: "Six assorted macarons.", image: "/img/macaron.jpg" },
5283
- { name: "Custom Birthday Cake", slug: "birthday-cake", price: "250.00", category: "cakes", description: "Made-to-order. Min 48h notice.", image: "/img/birthday.jpg" }
5284
- ];
5285
- var CATEGORIES = [
5286
- { slug: "breads", name: "Breads", description: "Sourdough, baguettes, focaccia." },
5287
- { slug: "pastries", name: "Pastries", description: "Croissants, danishes, brownies." },
5288
- { slug: "cakes", name: "Cakes", description: "Cake slices and custom orders." },
5289
- { slug: "drinks", name: "Drinks", description: "Coffee, tea, juices." }
5290
- ];
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
+ }
5291
5696
  function seedDefault(registry) {
5292
- const businessId = "bus_default_akua_bakery";
5293
5697
  const business = {
5294
- id: businessId,
5698
+ id: BUSINESS_ID,
5295
5699
  name: "Akua's Bakery",
5296
5700
  handle: "akua",
5297
5701
  business_type: "eatery",
5298
5702
  email: "hello@akuasbakery.test",
5299
5703
  default_currency: "GHS",
5300
- default_phone: "+233244000000",
5704
+ default_phone: "+233 244 000 000",
5301
5705
  default_address: "12 Independence Ave, Accra",
5302
5706
  default_offers_table_service: true,
5303
5707
  default_accepts_online_orders: true,
5304
- image: "/img/akua-logo.png",
5708
+ image: img("1568254183919-78a4f43a2877"),
5305
5709
  status: "active",
5306
5710
  created_at: ISO,
5307
5711
  updated_at: ISO,
5308
5712
  created_by: "seed",
5309
- 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
+ },
5310
5720
  is_online_only: false,
5311
5721
  enabled_payment_types: ["mobile_money", "card", "cash"],
5312
5722
  default_location_settings: {},
5313
5723
  country_code: "GH",
5314
5724
  timezone: "Africa/Accra"
5315
5725
  };
5316
- registry.businesses.put(businessId, business);
5317
- const categoryIdsBySlug = /* @__PURE__ */ new Map();
5318
- 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) {
5319
5736
  const id = `cat_${c.slug}`;
5320
- categoryIdsBySlug.set(c.slug, id);
5737
+ catIds.set(c.slug, id);
5321
5738
  const cat = {
5322
5739
  id,
5323
- business_id: businessId,
5740
+ business_id: BUSINESS_ID,
5324
5741
  name: c.name,
5325
5742
  slug: c.slug,
5326
5743
  description: c.description,
@@ -5332,99 +5749,354 @@ function seedDefault(registry) {
5332
5749
  };
5333
5750
  registry.categories.put(id, cat);
5334
5751
  }
5335
- const featured = [];
5336
- PRODUCTS.forEach((p, i) => {
5337
- const id = `prod_${p.slug}`;
5338
- const catId = categoryIdsBySlug.get(p.category);
5339
- const product = {
5340
- id,
5341
- business_id: businessId,
5342
- name: p.name,
5343
- slug: p.slug,
5344
- description: p.description,
5345
- product_type: "product",
5346
- base_price: p.price,
5347
- currency: "GHS",
5348
- image: p.image,
5349
- images: [p.image],
5350
- is_available: true,
5351
- category_ids: [catId],
5352
- collection_ids: [],
5353
- add_on_ids: [],
5354
- variant_ids: [],
5355
- created_at: ISO,
5356
- updated_at: ISO
5357
- };
5358
- registry.products.put(id, product);
5359
- const cat = registry.categories.get(catId);
5360
- cat.product_ids.push(id);
5361
- registry.categories.put(catId, cat);
5362
- if (i < 5) featured.push(id);
5363
- });
5364
- 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 = {
5365
6065
  id: "col_featured",
5366
- business_id: businessId,
5367
- name: "Featured",
6066
+ business_id: BUSINESS_ID,
6067
+ name: "Signature & Featured",
5368
6068
  slug: "featured",
5369
- description: "Our most popular items.",
5370
- product_ids: featured,
6069
+ description: "Our most-loved bakes and a few seasonal stars.",
6070
+ product_ids: featuredIds,
5371
6071
  is_active: true,
5372
6072
  created_at: ISO,
5373
6073
  updated_at: ISO
5374
6074
  };
5375
- registry.collections.put(featuredCollection.id, featuredCollection);
5376
- for (const pid of featured) {
6075
+ registry.collections.put(featured.id, featured);
6076
+ for (const pid of featuredIds) {
5377
6077
  const p = registry.products.get(pid);
5378
6078
  if (p) {
5379
- p.collection_ids.push(featuredCollection.id);
6079
+ p.collection_ids.push(featured.id);
5380
6080
  registry.products.put(pid, p);
5381
6081
  }
5382
6082
  }
5383
- const tasting = {
5384
- id: "svc_tasting",
5385
- business_id: businessId,
5386
- name: "Bakery Tasting Session",
5387
- description: "Walk through our pastry menu with our head baker.",
5388
- duration_minutes: 60,
5389
- price: "150.00",
5390
- is_available: true
5391
- };
5392
- registry.services.put(tasting.id, tasting);
5393
- const cake = {
5394
- id: "svc_custom_cake",
5395
- business_id: businessId,
5396
- name: "Custom Cake Consultation",
5397
- description: "Design a custom cake for your event.",
5398
- duration_minutes: 30,
5399
- price: "0.00",
5400
- is_available: true
5401
- };
5402
- registry.services.put(cake.id, cake);
5403
- const sizeAxisId = "axis_size";
5404
- const flavourAxisId = "axis_flavour";
5405
- const sizeValues = [
5406
- { id: "axv_size_small", name: 'Small (8")' },
5407
- { id: "axv_size_medium", name: 'Medium (10")' },
5408
- { id: "axv_size_large", name: 'Large (12")' }
5409
- ];
5410
- const flavourValues = [
5411
- { id: "axv_flavour_chocolate", name: "Chocolate" },
5412
- { id: "axv_flavour_vanilla", name: "Vanilla" },
5413
- { id: "axv_flavour_red_velvet", name: "Red Velvet" }
5414
- ];
5415
6083
  const sizeAxis = {
5416
- id: sizeAxisId,
5417
- business_id: businessId,
6084
+ id: "axis_cake_size",
6085
+ business_id: BUSINESS_ID,
5418
6086
  product_id: "prod_birthday-cake",
5419
6087
  name: "Size",
5420
6088
  display_order: 0,
5421
6089
  affects_recipe: false,
5422
- 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) => ({
5423
6095
  id: v.id,
5424
- business_id: businessId,
5425
- axis_id: sizeAxisId,
6096
+ business_id: BUSINESS_ID,
6097
+ axis_id: "axis_cake_size",
5426
6098
  name: v.name,
5427
- display_order: i,
6099
+ display_order: v.display,
5428
6100
  created_at: ISO,
5429
6101
  updated_at: ISO
5430
6102
  })),
@@ -5432,18 +6104,22 @@ function seedDefault(registry) {
5432
6104
  updated_at: ISO
5433
6105
  };
5434
6106
  const flavourAxis = {
5435
- id: flavourAxisId,
5436
- business_id: businessId,
6107
+ id: "axis_cake_flavour",
6108
+ business_id: BUSINESS_ID,
5437
6109
  product_id: "prod_birthday-cake",
5438
6110
  name: "Flavour",
5439
6111
  display_order: 1,
5440
6112
  affects_recipe: true,
5441
- 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) => ({
5442
6118
  id: v.id,
5443
- business_id: businessId,
5444
- axis_id: flavourAxisId,
6119
+ business_id: BUSINESS_ID,
6120
+ axis_id: "axis_cake_flavour",
5445
6121
  name: v.name,
5446
- display_order: i,
6122
+ display_order: v.display,
5447
6123
  created_at: ISO,
5448
6124
  updated_at: ISO
5449
6125
  })),
@@ -5452,40 +6128,42 @@ function seedDefault(registry) {
5452
6128
  };
5453
6129
  registry.variantAxes.put(sizeAxis.id, sizeAxis);
5454
6130
  registry.variantAxes.put(flavourAxis.id, flavourAxis);
5455
- const variantPrices = ["250.00", "350.00", "450.00"];
5456
- const variantIds = [];
5457
- let isFirst = true;
5458
- for (let s = 0; s < sizeValues.length; s++) {
5459
- for (let f = 0; f < flavourValues.length; f++) {
5460
- 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}`;
5461
6139
  const variant = {
5462
6140
  id,
5463
6141
  product_id: "prod_birthday-cake",
5464
- business_id: businessId,
5465
- name: `${sizeValues[s].name} \u2022 ${flavourValues[f].name}`,
5466
- sku: `BIRTHDAY-${sizeValues[s].id.split("_").pop()}-${flavourValues[f].id.split("_").pop()}`,
5467
- 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",
5468
6146
  component_multiplier: "1.00",
5469
- is_default: isFirst,
6147
+ is_default: firstVariant,
5470
6148
  is_active: true,
5471
- axis_value_ids: [sizeValues[s].id, flavourValues[f].id],
6149
+ axis_value_ids: [sz.id, fl.id],
5472
6150
  created_at: ISO,
5473
6151
  updated_at: ISO
5474
6152
  };
5475
6153
  registry.variants.put(id, variant);
5476
- variantIds.push(id);
5477
- isFirst = false;
6154
+ cakeVariantIds.push(id);
6155
+ firstVariant = false;
5478
6156
  }
5479
6157
  }
5480
- const birthday = registry.products.get("prod_birthday-cake");
5481
- if (birthday) {
5482
- birthday.variant_ids = variantIds;
5483
- 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);
5484
6162
  }
5485
6163
  const toppingsAddOn = {
5486
6164
  id: "addon_toppings",
5487
- business_id: businessId,
5488
- name: "Toppings",
6165
+ business_id: BUSINESS_ID,
6166
+ name: "Cake toppings",
5489
6167
  is_multiple_allowed: true,
5490
6168
  is_required: false,
5491
6169
  is_mutually_exclusive: false,
@@ -5494,15 +6172,14 @@ function seedDefault(registry) {
5494
6172
  created_at: ISO,
5495
6173
  updated_at: ISO,
5496
6174
  options: [
5497
- { 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 },
5498
- { 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 },
5499
- { 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 }
5500
6178
  ]
5501
6179
  };
5502
- registry.addOns.put(toppingsAddOn.id, toppingsAddOn);
5503
6180
  const milkAddOn = {
5504
6181
  id: "addon_milk",
5505
- business_id: businessId,
6182
+ business_id: BUSINESS_ID,
5506
6183
  name: "Milk",
5507
6184
  is_multiple_allowed: false,
5508
6185
  is_required: false,
@@ -5512,17 +6189,17 @@ function seedDefault(registry) {
5512
6189
  created_at: ISO,
5513
6190
  updated_at: ISO,
5514
6191
  options: [
5515
- { 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 },
5516
- { 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 },
5517
- { 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 }
5518
6195
  ]
5519
6196
  };
6197
+ registry.addOns.put(toppingsAddOn.id, toppingsAddOn);
5520
6198
  registry.addOns.put(milkAddOn.id, milkAddOn);
5521
6199
  const cakeProductIds = ["prod_chocolate-cake", "prod_red-velvet", "prod_carrot-cake", "prod_cheesecake", "prod_birthday-cake"];
5522
6200
  const drinkProductIds = ["prod_coffee", "prod_tea"];
5523
6201
  for (const pid of cakeProductIds) {
5524
- const link = { product_id: pid, add_on_id: toppingsAddOn.id };
5525
- registry.productAddOns.put(`${pid}:${toppingsAddOn.id}`, link);
6202
+ registry.productAddOns.put(`${pid}:${toppingsAddOn.id}`, { product_id: pid, add_on_id: toppingsAddOn.id });
5526
6203
  const p = registry.products.get(pid);
5527
6204
  if (p) {
5528
6205
  p.add_on_ids.push(toppingsAddOn.id);
@@ -5530,8 +6207,7 @@ function seedDefault(registry) {
5530
6207
  }
5531
6208
  }
5532
6209
  for (const pid of drinkProductIds) {
5533
- const link = { product_id: pid, add_on_id: milkAddOn.id };
5534
- registry.productAddOns.put(`${pid}:${milkAddOn.id}`, link);
6210
+ registry.productAddOns.put(`${pid}:${milkAddOn.id}`, { product_id: pid, add_on_id: milkAddOn.id });
5535
6211
  const p = registry.products.get(pid);
5536
6212
  if (p) {
5537
6213
  p.add_on_ids.push(milkAddOn.id);
@@ -5540,7 +6216,7 @@ function seedDefault(registry) {
5540
6216
  }
5541
6217
  const breakfastBundle = {
5542
6218
  id: "bnd_breakfast_box",
5543
- business_id: businessId,
6219
+ business_id: BUSINESS_ID,
5544
6220
  product_id: "prod_breakfast-box",
5545
6221
  name: "Breakfast Box",
5546
6222
  slug: "breakfast-box",
@@ -5552,29 +6228,9 @@ function seedDefault(registry) {
5552
6228
  updated_at: ISO
5553
6229
  };
5554
6230
  registry.bundles.put(breakfastBundle.id, breakfastBundle);
5555
- const breakfastProduct = {
5556
- id: "prod_breakfast-box",
5557
- business_id: businessId,
5558
- name: "Breakfast Box",
5559
- slug: "breakfast-box",
5560
- description: "Bundled breakfast for one.",
5561
- product_type: "bundle",
5562
- base_price: "30.00",
5563
- currency: "GHS",
5564
- image: "/img/breakfast-box.jpg",
5565
- images: ["/img/breakfast-box.jpg"],
5566
- is_available: true,
5567
- category_ids: [categoryIdsBySlug.get("pastries")],
5568
- collection_ids: [],
5569
- add_on_ids: [],
5570
- variant_ids: [],
5571
- created_at: ISO,
5572
- updated_at: ISO
5573
- };
5574
- registry.products.put(breakfastProduct.id, breakfastProduct);
5575
6231
  const pastryComposite = {
5576
6232
  id: "comp_pastry_box",
5577
- business_id: businessId,
6233
+ business_id: BUSINESS_ID,
5578
6234
  product_id: "prod_pastry-box",
5579
6235
  base_price: "60.00",
5580
6236
  pricing_mode: "additive",
@@ -5586,43 +6242,51 @@ function seedDefault(registry) {
5586
6242
  name: "Choose your pastries",
5587
6243
  min: 6,
5588
6244
  max: 12,
5589
- 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
+ ]
5590
6254
  }
5591
6255
  ],
5592
6256
  created_at: ISO,
5593
6257
  updated_at: ISO
5594
6258
  };
5595
6259
  registry.composites.put(pastryComposite.id, pastryComposite);
5596
- const pastryBoxProduct = {
5597
- id: "prod_pastry-box",
5598
- business_id: businessId,
5599
- name: "Build-Your-Own Pastry Box",
5600
- slug: "pastry-box",
5601
- description: "Pick 6 to 12 pastries \u2014 mix and match.",
5602
- product_type: "composite",
5603
- base_price: "60.00",
5604
- currency: "GHS",
5605
- image: "/img/pastry-box.jpg",
5606
- images: ["/img/pastry-box.jpg"],
5607
- is_available: true,
5608
- category_ids: [categoryIdsBySlug.get("pastries")],
5609
- collection_ids: [],
5610
- add_on_ids: [],
5611
- variant_ids: [],
5612
- created_at: ISO,
5613
- 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
5614
6268
  };
5615
- registry.products.put(pastryBoxProduct.id, pastryBoxProduct);
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
6277
+ };
6278
+ registry.services.put(tasting.id, tasting);
6279
+ registry.services.put(consult.id, consult);
5616
6280
  const tags = [
5617
- { id: "tag_signature", business_id: businessId, name: "Signature", slug: "signature", color: "#d97706", sort_order: 0, usage_count: 5, created_at: ISO, updated_at: ISO },
5618
- { id: "tag_vegan", business_id: businessId, name: "Vegan", slug: "vegan", color: "#16a34a", sort_order: 1, usage_count: 3, created_at: ISO, updated_at: ISO },
5619
- { 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 },
5620
- { 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 }
5621
6285
  ];
5622
6286
  for (const t of tags) registry.tags.put(t.id, t);
5623
6287
  const baked = {
5624
6288
  id: "tax_baked_goods",
5625
- business_id: businessId,
6289
+ business_id: BUSINESS_ID,
5626
6290
  name: "Baked Goods",
5627
6291
  slug: "baked-goods",
5628
6292
  path: ["Food", "Baked Goods"],
@@ -5632,7 +6296,7 @@ function seedDefault(registry) {
5632
6296
  };
5633
6297
  const beverages = {
5634
6298
  id: "tax_beverages",
5635
- business_id: businessId,
6299
+ business_id: BUSINESS_ID,
5636
6300
  name: "Beverages",
5637
6301
  slug: "beverages",
5638
6302
  path: ["Food", "Beverages"],
@@ -5645,13 +6309,13 @@ function seedDefault(registry) {
5645
6309
  const attrs = [
5646
6310
  {
5647
6311
  id: "attr_allergens",
5648
- business_id: businessId,
6312
+ business_id: BUSINESS_ID,
5649
6313
  namespace: "core",
5650
6314
  name: "Allergens",
5651
6315
  slug: "allergens",
5652
6316
  description: "Common allergens present in this item.",
5653
6317
  attribute_type: "multi_select",
5654
- options: ["wheat", "eggs", "dairy", "nuts", "soy"],
6318
+ options: ["wheat", "gluten", "eggs", "dairy", "nuts", "soy", "sesame"],
5655
6319
  is_required: false,
5656
6320
  is_filterable: true,
5657
6321
  visibility: "public",
@@ -5662,7 +6326,7 @@ function seedDefault(registry) {
5662
6326
  },
5663
6327
  {
5664
6328
  id: "attr_shelf_life",
5665
- business_id: businessId,
6329
+ business_id: BUSINESS_ID,
5666
6330
  namespace: "core",
5667
6331
  name: "Shelf life",
5668
6332
  slug: "shelf-life",
@@ -5677,7 +6341,7 @@ function seedDefault(registry) {
5677
6341
  },
5678
6342
  {
5679
6343
  id: "attr_caffeine",
5680
- business_id: businessId,
6344
+ business_id: BUSINESS_ID,
5681
6345
  namespace: "core",
5682
6346
  name: "Caffeine",
5683
6347
  slug: "caffeine",
@@ -5692,7 +6356,7 @@ function seedDefault(registry) {
5692
6356
  },
5693
6357
  {
5694
6358
  id: "attr_seasonal",
5695
- business_id: businessId,
6359
+ business_id: BUSINESS_ID,
5696
6360
  namespace: "core",
5697
6361
  name: "Seasonal",
5698
6362
  slug: "seasonal",
@@ -5710,10 +6374,10 @@ function seedDefault(registry) {
5710
6374
  const articles = [
5711
6375
  {
5712
6376
  id: "kb_delivery",
5713
- business_id: businessId,
5714
- title: "Delivery & Pickup",
6377
+ business_id: BUSINESS_ID,
6378
+ title: "Delivery & pickup",
5715
6379
  slug: "delivery-pickup",
5716
- 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.",
5717
6381
  category: "logistics",
5718
6382
  tags: ["delivery", "pickup"],
5719
6383
  created_at: ISO,
@@ -5721,10 +6385,10 @@ function seedDefault(registry) {
5721
6385
  },
5722
6386
  {
5723
6387
  id: "kb_custom_cakes",
5724
- business_id: businessId,
5725
- title: "Ordering custom cakes",
6388
+ business_id: BUSINESS_ID,
6389
+ title: "Custom cakes & celebrations",
5726
6390
  slug: "custom-cakes",
5727
- 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.",
5728
6392
  category: "products",
5729
6393
  tags: ["cakes", "custom"],
5730
6394
  created_at: ISO,
@@ -5732,10 +6396,10 @@ function seedDefault(registry) {
5732
6396
  },
5733
6397
  {
5734
6398
  id: "kb_allergens",
5735
- business_id: businessId,
5736
- title: "Allergens",
6399
+ business_id: BUSINESS_ID,
6400
+ title: "Allergen information",
5737
6401
  slug: "allergens",
5738
- 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.",
5739
6403
  category: "products",
5740
6404
  tags: ["allergens", "safety"],
5741
6405
  created_at: ISO,
@@ -5743,7 +6407,7 @@ function seedDefault(registry) {
5743
6407
  }
5744
6408
  ];
5745
6409
  for (const a of articles) registry.knowledgeArticles.put(a.id, a);
5746
- return { businessId };
6410
+ return { businessId: BUSINESS_ID };
5747
6411
  }
5748
6412
 
5749
6413
  // src/mock/seeds/empty.ts
@@ -7846,195 +8510,47 @@ function createDeps(options = {}) {
7846
8510
  }
7847
8511
 
7848
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
+ }
7849
8534
  function createMockApp(options = {}) {
7850
8535
  const deps = createDeps(options);
7851
8536
  const app = new Hono2();
7852
- const corsOrigins = options.cors === "*" ? "*" : options.cors;
7853
- if (corsOrigins) {
7854
- app.use("*", async (c, next) => {
7855
- const origin = c.req.header("origin") ?? "";
7856
- const allow = corsOrigins === "*" ? "*" : corsOrigins.includes(origin) ? origin : corsOrigins[0] ?? "*";
7857
- const headers = {
7858
- "Access-Control-Allow-Origin": allow,
7859
- "Access-Control-Allow-Credentials": "true",
7860
- "Access-Control-Allow-Headers": c.req.header("access-control-request-headers") ?? "content-type,authorization,x-session-token,idempotency-key",
7861
- "Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
7862
- "Access-Control-Expose-Headers": "x-session-token",
7863
- "Access-Control-Max-Age": "86400",
7864
- Vary: "Origin"
7865
- };
7866
- if (c.req.method === "OPTIONS") {
7867
- return new Response(null, { status: 204, headers });
7868
- }
7869
- for (const [k, v] of Object.entries(headers)) c.header(k, v);
7870
- await next();
7871
- });
7872
- }
8537
+ const cors = options.cors ?? "*";
8538
+ if (cors) app.use("*", corsMiddleware(cors));
7873
8539
  app.get(
7874
8540
  "/",
7875
8541
  (c) => c.json({
7876
- name: "@cimplify/sdk/mock",
7877
- version: "0.36.0",
8542
+ name: PACKAGE_NAME,
7878
8543
  docs: "https://github.com/cimplify/cimplify",
7879
8544
  api: "/api/v1",
7880
8545
  admin: "/_mock",
7881
8546
  elements: "/elements",
7882
- seed_business_id: deps.defaultBusinessId
8547
+ images: "/img/<slug>",
8548
+ seed_business_id: deps.defaultBusinessId,
8549
+ time: deps.clock.iso()
7883
8550
  })
7884
8551
  );
7885
8552
  app.get("/healthz", (c) => c.json({ ok: true, time: deps.clock.iso() }));
7886
- const UNSPLASH_PHOTOS = {
7887
- // Akua's Bakery (default seed)
7888
- sourdough: "1509440159596-0249088772ff",
7889
- wheat: "1486887396153-fa416526c108",
7890
- baguette: "1568471173242-461f0a730452",
7891
- focaccia: "1573472635068-fd9b1abdd8e5",
7892
- croissant: "1555507036-ab1f4038808a",
7893
- pac: "1530610476181-d83430b64dcd",
7894
- danish: "1517433367423-c7e5b0f35086",
7895
- cinnamon: "1583527976767-50068e72b7a3",
7896
- "choc-cake": "1565958011703-44f9829ba187",
7897
- "red-velvet": "1586788680434-30d324b2d46f",
7898
- carrot: "1568827999250-3f6afff96e66",
7899
- cheesecake: "1567306226416-28f0efdc88ce",
7900
- coffee: "1495474472287-4d71bcdd2085",
7901
- tea: "1576092768241-dec231879fc3",
7902
- sobolo: "1556679343-c7306c1976bc",
7903
- oj: "1600271886742-f049b969dc7a",
7904
- banana: "1632931057819-4eefffa8e007",
7905
- brownie: "1551024506-0bccd828d307",
7906
- macaron: "1558326567-98ae2405596b",
7907
- birthday: "1535254973040-607b474cb50d",
7908
- "breakfast-box": "1533089860892-a7c6f0a88666",
7909
- "pastry-box": "1486427944299-d1955d23e34d",
7910
- // Mama's Kitchen (restaurant seed)
7911
- kelewele: "1585536830049-aedf5c1b98a4",
7912
- "spring-rolls": "1606755962773-d324e0a13086",
7913
- "soup-of-the-day": "1547592180-85f173990554",
7914
- "jollof-rice": "1604329760661-e71dc83f8f26",
7915
- "banku-tilapia": "1559847844-5315695dadae",
7916
- "fufu-light-soup": "1547928576-b822bcced36e",
7917
- waakye: "1604329760661-e71dc83f8f26",
7918
- "grilled-chicken": "1532550907401-a500c9a57435",
7919
- burger: "1568901346375-23c9450c58cd",
7920
- "yam-fries": "1573080496219-bb080dd4f877",
7921
- "fried-plantain": "1604329760661-e71dc83f8f26",
7922
- salad: "1512621776951-a57141f2eefd",
7923
- "ice-cream": "1497034825429-c343d7c6a68f",
7924
- "cake-slice": "1565958011703-44f9829ba187",
7925
- "fruit-bowl": "1490474418585-ba9bad8fd0ea",
7926
- "soft-drink": "1497534446932-c925b458314e",
7927
- "fresh-juice": "1622597467836-f3285f2131b8",
7928
- water: "1548839140-29a749e1cf4d",
7929
- feast: "1546069901-ba9599a7e63c",
7930
- byo: "1546069901-ba9599a7e63c",
7931
- // Currents Electronics
7932
- "android-flagship": "1511707171634-5f897ff02aa9",
7933
- "android-mid": "1592899677977-9c10ca588bbd",
7934
- "phone-budget": "1574944985070-8f3ebc6b79d2",
7935
- "laptop-pro": "1496181133206-80ce9b88a853",
7936
- "laptop-air": "1517336714731-489689fd1ca8",
7937
- "laptop-budget": "1541807084-5c52b6b3adef",
7938
- "headphones-anc": "1583394838336-acd977736f90",
7939
- "earbuds-pro": "1606220945770-b5b6c2c55bf1",
7940
- "speaker-bt": "1608043152269-423dbba4e7e1",
7941
- "phone-case": "1601593346740-925612772716",
7942
- "screen-protector": "1592890288564-76628a30a657",
7943
- "fast-charger": "1583394838336-acd977736f90",
7944
- "laptop-sleeve": "1525547719571-a2d4ac8945e2",
7945
- "wireless-mouse": "1527864550417-7fd91fc51a46",
7946
- "mech-keyboard": "1587829741301-dc798b83add3",
7947
- "gaming-controller": "1592840496694-26d035b52b48",
7948
- "gaming-headset": "1591105327764-407a4a06e768",
7949
- "console-handheld": "1606144042614-b2417e99c4e3",
7950
- wfh: "1593642632559-0c6d3fc62b89",
7951
- "game-pack": "1606144042614-b2417e99c4e3",
7952
- // Reesa apparel
7953
- "ankara-shirt": "1591047139829-d91aecb6caea",
7954
- "agbada-set": "1605497788044-5a32c7078486",
7955
- kaftan: "1554412933-514a83d2f3c8",
7956
- "leather-tote": "1584917865442-de89df76afd3",
7957
- sandals: "1543163521-1bf539c55dd2",
7958
- "beaded-bracelet": "1611591437281-460bfbe1220a",
7959
- owambe: "1605497788044-5a32c7078486",
7960
- // FreshMart
7961
- "tomatoes-1kg": "1546470427-227f1c75a9ef",
7962
- "onions-1kg": "1620574387735-3624d75b2dbc",
7963
- "ginger-200g": "1606937330526-f5e6a2a83555",
7964
- "bananas-bunch": "1571771894821-ce9b6c11b08e",
7965
- pineapple: "1550258987-190a2d41a8ba",
7966
- "spinach-bunch": "1576045057995-568f588f82fb",
7967
- "milk-1l": "1550583724-b2692b85b150",
7968
- "yogurt-500g": "1571212515416-fef01fc43637",
7969
- "eggs-12": "1582722872445-44dc5f7e3c8f",
7970
- "cheese-200g": "1486297678162-eb2a19b0a32d",
7971
- "chicken-1kg": "1604503468506-a8da13d82791",
7972
- "beef-mince-500g": "1607623814075-e51df1bdc82f",
7973
- "tilapia-each": "1546069901-ba9599a7e63c",
7974
- "rice-5kg": "1586201375761-83865001e31c",
7975
- "garri-2kg": "1604329760661-e71dc83f8f26",
7976
- "palm-oil-1l": "1474979266404-7eaacbcd87c5",
7977
- "vegetable-oil-1l": "1474979266404-7eaacbcd87c5",
7978
- "salt-1kg": "1612531386530-97286d97c2d2",
7979
- "sugar-1kg": "1581281697996-3aef0c3b9b12",
7980
- "tomato-paste": "1546470427-227f1c75a9ef",
7981
- "soap-bar": "1583912267550-d6c2ac3196c0",
7982
- "detergent-1kg": "1583912267550-d6c2ac3196c0",
7983
- "tissue-pack": "1610557892470-55d9e80c0bce",
7984
- "trash-bags": "1583912267550-d6c2ac3196c0",
7985
- essentials: "1542838132-92c53300491e",
7986
- // Serene Spa services
7987
- "swedish-massage-60": "1544161515-4ab6ce6db874",
7988
- "swedish-massage-90": "1544161515-4ab6ce6db874",
7989
- "deep-tissue-60": "1600334129128-685c5582fd35",
7990
- "couples-massage": "1540555700478-4be289fbecef",
7991
- "hot-stone-90": "1600334129128-685c5582fd35",
7992
- "facial-classic": "1570172619644-dfd03ed5d881",
7993
- "facial-anti-aging": "1571019613454-1cb2f99b2d8b",
7994
- "facial-acne": "1571019613454-1cb2f99b2d8b",
7995
- manicure: "1604654894610-df63bc536371",
7996
- pedicure: "1610701596007-11502861dcfa",
7997
- "gel-manicure": "1604654894610-df63bc536371",
7998
- haircut: "1562322140-8baeececf3df",
7999
- "hair-treatment": "1522337360788-8b13dee7a37e",
8000
- color: "1562322140-8baeececf3df",
8001
- "yoga-session": "1545205597-3d9d02c29597",
8002
- meditation: "1545389336-cf090694435e",
8003
- "spa-day": "1540555700478-4be289fbecef",
8004
- "couples-retreat": "1540555700478-4be289fbecef",
8005
- "custom-experience": "1545389336-cf090694435e"
8006
- };
8007
- app.get("/img/*", (c) => {
8008
- const path = c.req.path.replace(/^\/img\//, "").replace(/\.[a-z0-9]+$/i, "");
8009
- const slug = path.split("/").pop() ?? "";
8010
- const photoId = UNSPLASH_PHOTOS[slug];
8011
- if (photoId) {
8012
- return c.redirect(`https://images.unsplash.com/photo-${photoId}?w=600&h=600&fit=crop&auto=format&q=80`, 302);
8013
- }
8014
- const label = slug.replace(/[-_]+/g, " ").trim() || "product";
8015
- let hash = 0;
8016
- for (let i = 0; i < slug.length; i++) hash = hash * 31 + slug.charCodeAt(i) >>> 0;
8017
- const hue = hash % 360;
8018
- const display = label.length > 20 ? label.slice(0, 20) + "\u2026" : label;
8019
- const svg = `<?xml version="1.0" encoding="UTF-8"?>
8020
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400">
8021
- <defs>
8022
- <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
8023
- <stop offset="0%" stop-color="hsl(${hue}, 65%, 65%)"/>
8024
- <stop offset="100%" stop-color="hsl(${(hue + 40) % 360}, 70%, 45%)"/>
8025
- </linearGradient>
8026
- </defs>
8027
- <rect width="400" height="400" fill="url(#g)"/>
8028
- <text x="200" y="210" font-family="-apple-system, BlinkMacSystemFont, Inter, sans-serif" font-size="22" font-weight="600" fill="white" text-anchor="middle" dominant-baseline="middle" style="text-transform: capitalize">${display.replace(/[<>&]/g, "")}</text>
8029
- </svg>`;
8030
- return new Response(svg, {
8031
- headers: {
8032
- "Content-Type": "image/svg+xml; charset=utf-8",
8033
- "Cache-Control": "public, max-age=3600",
8034
- "Access-Control-Allow-Origin": "*"
8035
- }
8036
- });
8037
- });
8553
+ mountImageRoutes(app);
8038
8554
  app.route("/api/v1", buildV1(deps));
8039
8555
  app.route("/_mock", buildAdmin(deps));
8040
8556
  app.route("/elements", buildElements(deps));
@@ -8042,7 +8558,11 @@ function createMockApp(options = {}) {
8042
8558
  (c) => c.json(
8043
8559
  {
8044
8560
  success: false,
8045
- 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
+ },
8046
8566
  hint: "See /api/v1 for the SDK surface and /_mock for the admin surface."
8047
8567
  },
8048
8568
  501