@cimplify/sdk 0.82.0 → 0.83.0

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.
package/dist/mock/cli.mjs CHANGED
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import { writeFileSync, existsSync, readFileSync } from 'fs';
3
+ import { basename, isAbsolute, resolve } from 'path';
4
+ import { pathToFileURL } from 'url';
2
5
  import { createServer } from 'http';
3
6
  import { Http2ServerRequest, constants } from 'http2';
4
7
  import { Readable } from 'stream';
5
8
  import crypto, { createHmac } from 'crypto';
6
- import { existsSync, readFileSync, writeFileSync } from 'fs';
7
9
 
8
10
  var RequestError = class extends Error {
9
11
  constructor(message, options) {
@@ -490,7 +492,7 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
490
492
  });
491
493
  if (!chunk) {
492
494
  if (i === 1) {
493
- await new Promise((resolve) => setTimeout(resolve));
495
+ await new Promise((resolve2) => setTimeout(resolve2));
494
496
  maxReadCount = 3;
495
497
  continue;
496
498
  }
@@ -6533,6 +6535,14 @@ function createLinkService({ clock, ids }) {
6533
6535
  };
6534
6536
  }
6535
6537
 
6538
+ // src/mock/state/snapshot.ts
6539
+ function applySnapshot(reg, data) {
6540
+ for (const [k, v] of Object.entries(data)) {
6541
+ const store = reg[k];
6542
+ if (store) store.loadJSON(v);
6543
+ }
6544
+ }
6545
+
6536
6546
  // src/mock/seeds/image.ts
6537
6547
  var CDN_BASE_URL = "https://static-tmp.cimplify.io";
6538
6548
  function seedImage(industry, slug) {
@@ -10900,15 +10910,214 @@ function applySeed(registry, name) {
10900
10910
  return fn(registry);
10901
10911
  }
10902
10912
 
10913
+ // src/mock/seeds/context.ts
10914
+ var SEED_ISO = "2026-01-01T00:00:00.000Z";
10915
+ var ID_PREFIX = {
10916
+ business: "bus_",
10917
+ category: "cat_",
10918
+ collection: "col_",
10919
+ product: "prod_",
10920
+ service: "svc_"
10921
+ };
10922
+ var DEFAULT_CURRENCY = "GHS";
10923
+ function slugify(value) {
10924
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
10925
+ }
10926
+ function makeSeedContext(registry) {
10927
+ let current;
10928
+ const requireBusiness = () => {
10929
+ if (current) return current;
10930
+ const sole = registry.businesses.list();
10931
+ if (sole.length === 1) {
10932
+ current = sole[0];
10933
+ return current;
10934
+ }
10935
+ throw new Error("Seed must create a business (ctx.business) before adding child records.");
10936
+ };
10937
+ const resolveCategoryId = (cat) => {
10938
+ if (!cat) return void 0;
10939
+ if (typeof cat !== "string") return cat.id;
10940
+ if (registry.categories.get(cat)) return cat;
10941
+ return registry.categories.list().find((c) => c.slug === cat)?.id;
10942
+ };
10943
+ const resolveCollectionId = (col) => {
10944
+ if (!col) return void 0;
10945
+ if (typeof col !== "string") return col.id;
10946
+ if (registry.collections.get(col)) return col;
10947
+ return registry.collections.list().find((c) => c.slug === col)?.id;
10948
+ };
10949
+ return {
10950
+ registry,
10951
+ business(input) {
10952
+ const handle = input.handle ?? slugify(input.name);
10953
+ const id = `${ID_PREFIX.business}${slugify(handle)}`;
10954
+ const business = {
10955
+ business_type: "other",
10956
+ email: `hello@${handle}.test`,
10957
+ default_currency: DEFAULT_CURRENCY,
10958
+ default_offers_table_service: false,
10959
+ default_accepts_online_orders: true,
10960
+ status: "active",
10961
+ created_by: "seed",
10962
+ preferences: {},
10963
+ is_online_only: true,
10964
+ enabled_payment_types: ["mobile_money"],
10965
+ default_location_settings: {},
10966
+ country_code: "GH",
10967
+ timezone: "Africa/Accra",
10968
+ ...input,
10969
+ id,
10970
+ handle,
10971
+ created_at: input.created_at ?? SEED_ISO,
10972
+ updated_at: input.updated_at ?? SEED_ISO
10973
+ };
10974
+ registry.businesses.put(id, business);
10975
+ current = business;
10976
+ return business;
10977
+ },
10978
+ category(input) {
10979
+ const business = requireBusiness();
10980
+ const slug = input.slug ?? slugify(input.name);
10981
+ const id = `${ID_PREFIX.category}${slug}`;
10982
+ const category = {
10983
+ id,
10984
+ business_id: business.id,
10985
+ name: input.name,
10986
+ slug,
10987
+ description: input.description,
10988
+ image: input.image,
10989
+ product_ids: [],
10990
+ display_order: input.displayOrder ?? 0,
10991
+ is_active: true,
10992
+ created_at: SEED_ISO,
10993
+ updated_at: SEED_ISO
10994
+ };
10995
+ registry.categories.put(id, category);
10996
+ return category;
10997
+ },
10998
+ collection(input) {
10999
+ const business = requireBusiness();
11000
+ const slug = input.slug ?? slugify(input.name);
11001
+ const id = `${ID_PREFIX.collection}${slug}`;
11002
+ const collection = {
11003
+ id,
11004
+ business_id: business.id,
11005
+ name: input.name,
11006
+ slug,
11007
+ description: input.description,
11008
+ product_ids: [],
11009
+ is_active: true,
11010
+ created_at: SEED_ISO,
11011
+ updated_at: SEED_ISO
11012
+ };
11013
+ registry.collections.put(id, collection);
11014
+ return collection;
11015
+ },
11016
+ product(input) {
11017
+ const business = requireBusiness();
11018
+ const slug = input.slug ?? slugify(input.name);
11019
+ const id = `${ID_PREFIX.product}${slug}`;
11020
+ const categoryId = resolveCategoryId(input.category);
11021
+ const collectionId = resolveCollectionId(input.collection);
11022
+ const product = {
11023
+ id,
11024
+ business_id: business.id,
11025
+ name: input.name,
11026
+ slug,
11027
+ description: input.description,
11028
+ product_type: input.productType ?? "product",
11029
+ base_price: input.price,
11030
+ currency: input.currency ?? business.default_currency,
11031
+ image: input.image,
11032
+ images: input.images ?? (input.image ? [input.image] : []),
11033
+ is_available: true,
11034
+ category_ids: categoryId ? [categoryId] : [],
11035
+ collection_ids: collectionId ? [collectionId] : [],
11036
+ add_on_ids: [],
11037
+ variant_ids: [],
11038
+ tags: input.tags ?? [],
11039
+ calories: input.calories,
11040
+ allergies: input.allergies ?? [],
11041
+ ingredients: input.ingredients ?? [],
11042
+ pairings: [],
11043
+ is_signature: input.isSignature ?? false,
11044
+ is_new: input.isNew ?? false,
11045
+ duration_minutes: input.durationMinutes,
11046
+ metadata: input.metadata,
11047
+ created_at: SEED_ISO,
11048
+ updated_at: SEED_ISO
11049
+ };
11050
+ registry.products.put(id, product);
11051
+ if (categoryId) {
11052
+ const cat = registry.categories.get(categoryId);
11053
+ if (cat && !cat.product_ids.includes(id)) cat.product_ids.push(id);
11054
+ }
11055
+ if (collectionId) {
11056
+ const col = registry.collections.get(collectionId);
11057
+ if (col && !col.product_ids.includes(id)) col.product_ids.push(id);
11058
+ }
11059
+ return product;
11060
+ },
11061
+ service(input) {
11062
+ const business = requireBusiness();
11063
+ const id = `${ID_PREFIX.service}${slugify(input.name)}`;
11064
+ const service = {
11065
+ id,
11066
+ business_id: business.id,
11067
+ name: input.name,
11068
+ description: input.description,
11069
+ duration_minutes: input.durationMinutes,
11070
+ price: input.price,
11071
+ is_available: true
11072
+ };
11073
+ registry.services.put(id, service);
11074
+ return service;
11075
+ },
11076
+ image: seedImage,
11077
+ extend(name) {
11078
+ const result = applySeed(registry, name);
11079
+ current = registry.businesses.get(result.businessId) ?? current;
11080
+ return result;
11081
+ }
11082
+ };
11083
+ }
11084
+
11085
+ // src/mock/seeds/source.ts
11086
+ var SNAPSHOT_META_KEY = "__meta";
11087
+ function loadSnapshot(registry, data, businessId) {
11088
+ applySnapshot(registry, data);
11089
+ if (businessId) return { businessId };
11090
+ const meta = data[SNAPSHOT_META_KEY];
11091
+ if (meta?.defaultBusinessId) return { businessId: meta.defaultBusinessId };
11092
+ const businesses = registry.businesses.list();
11093
+ if (businesses.length === 1) return { businessId: businesses[0].id };
11094
+ if (businesses.length === 0) {
11095
+ throw new Error("Seed snapshot has no business. Add one, or set __meta.defaultBusinessId.");
11096
+ }
11097
+ throw new Error(
11098
+ `Seed snapshot has ${businesses.length} businesses; set __meta.defaultBusinessId to pick one.`
11099
+ );
11100
+ }
11101
+ function resolveSeed(registry, source) {
11102
+ if (typeof source === "string") return applySeed(registry, source);
11103
+ switch (source.kind) {
11104
+ case "builtin":
11105
+ return applySeed(registry, source.name);
11106
+ case "json":
11107
+ return loadSnapshot(registry, source.data, source.businessId);
11108
+ case "fn":
11109
+ return source.seed(makeSeedContext(registry));
11110
+ }
11111
+ }
11112
+
10903
11113
  // src/mock/deps.ts
10904
11114
  function createDeps(options = {}) {
10905
11115
  const clock = createClock({ frozenAt: options.frozenAt });
10906
11116
  const ids = createIdGen(options.rngSeed);
10907
11117
  const bus = createBus({ idGen: () => ids.raw("evt"), clock: () => clock.now() });
10908
11118
  const registry = createStateRegistry();
10909
- const seedName = options.seed ?? "default";
10910
- const seedResult = applySeed(registry, seedName);
10911
- let defaultBusinessId = seedResult.businessId;
11119
+ const seedSource = options.seed ?? "default";
11120
+ let defaultBusinessId = resolveSeed(registry, seedSource).businessId;
10912
11121
  const auth = createAuthService({
10913
11122
  registry,
10914
11123
  clock,
@@ -10962,11 +11171,10 @@ function createDeps(options = {}) {
10962
11171
  return defaultBusinessId;
10963
11172
  },
10964
11173
  chaos,
10965
- resetAll(newSeed) {
11174
+ resetAll(next) {
10966
11175
  for (const store of Object.values(registry)) store.reset();
10967
11176
  bus.clear();
10968
- const r = applySeed(registry, newSeed ?? seedName);
10969
- defaultBusinessId = r.businessId;
11177
+ defaultBusinessId = resolveSeed(registry, next ?? seedSource).businessId;
10970
11178
  return defaultBusinessId;
10971
11179
  }
10972
11180
  };
@@ -11042,11 +11250,7 @@ function createMockApp(options = {}) {
11042
11250
  function loadFromFile(reg, path) {
11043
11251
  if (!existsSync(path)) return false;
11044
11252
  try {
11045
- const data = JSON.parse(readFileSync(path, "utf8"));
11046
- for (const [k, v] of Object.entries(data)) {
11047
- const store = reg[k];
11048
- if (store) store.loadJSON(v);
11049
- }
11253
+ applySnapshot(reg, JSON.parse(readFileSync(path, "utf8")));
11050
11254
  return true;
11051
11255
  } catch {
11052
11256
  return false;
@@ -11109,11 +11313,18 @@ function setupWebhooks(bus, opts) {
11109
11313
  }
11110
11314
 
11111
11315
  // src/mock/cli.ts
11316
+ var CONVENTION_FILES = [
11317
+ "cimplify.seed.json",
11318
+ "cimplify.seed.mjs",
11319
+ "cimplify.seed.js",
11320
+ "cimplify.seed.ts"
11321
+ ];
11112
11322
  function parseArgs(argv) {
11113
11323
  const opts = {
11114
11324
  port: Number(process.env.CIMPLIFY_MOCK_PORT) || 8787,
11115
11325
  host: process.env.CIMPLIFY_MOCK_HOST ?? "127.0.0.1",
11116
11326
  seed: "default",
11327
+ seedExplicit: false,
11117
11328
  authMode: "permissive",
11118
11329
  defaultOtp: "123456",
11119
11330
  cors: "*",
@@ -11132,6 +11343,16 @@ function parseArgs(argv) {
11132
11343
  break;
11133
11344
  case "--seed":
11134
11345
  opts.seed = next();
11346
+ opts.seedExplicit = true;
11347
+ break;
11348
+ case "--seed-file":
11349
+ opts.seedFile = next();
11350
+ break;
11351
+ case "--seed-module":
11352
+ opts.seedModule = next();
11353
+ break;
11354
+ case "--dump":
11355
+ opts.dump = next();
11135
11356
  break;
11136
11357
  case "--auth-mode":
11137
11358
  opts.authMode = next();
@@ -11185,14 +11406,24 @@ function printHelp() {
11185
11406
  Usage:
11186
11407
  cimplify-mock [flags]
11187
11408
 
11188
- Flags:
11409
+ Seed (choose one; flags win over a cimplify.seed.* file in the cwd):
11410
+ --seed <name> Built-in seed: default | empty | restaurant | retail |
11411
+ services | grocery | fashion | pharmacy | auto |
11412
+ reesa-storefront
11413
+ --seed-file <file.json> Boot from a JSON snapshot (dump one with --dump, edit, reload)
11414
+ --seed-module <file> Boot from a module default-exporting a seed function
11415
+ (.mjs/.js anywhere; .ts needs bun)
11416
+ --dump <file.json> Seed, write the state snapshot to <file>, and exit
11417
+ (convention) With no seed flag, a cimplify.seed.{json,mjs,js,ts} in
11418
+ the cwd is loaded automatically
11419
+
11420
+ Server:
11189
11421
  --port, -p <num> Port to listen on (default 8787)
11190
11422
  --host <host> Host to bind (default 127.0.0.1)
11191
- --seed <name> default | restaurant | retail | services | grocery | fashion | reesa-storefront | empty
11192
11423
  --auth-mode <mode> permissive (auto-mint sessions) | strict
11193
11424
  --otp <code> Default OTP code (default 123456)
11194
11425
  --seed-rng <n> Deterministic RNG seed
11195
- --persist <file> Persist state to JSON file
11426
+ --persist <file> Persist live state to a JSON file (restore on restart)
11196
11427
  --cors <origins> CORS origins (comma-separated, or '*')
11197
11428
  --webhook-url <url> Send mock events to this URL
11198
11429
  --webhook-secret <key> HMAC secret for webhook signatures
@@ -11202,16 +11433,75 @@ Flags:
11202
11433
  --help, -h
11203
11434
  `);
11204
11435
  }
11436
+ function resolvePath(file) {
11437
+ return isAbsolute(file) ? file : resolve(process.cwd(), file);
11438
+ }
11439
+ function readSnapshot(file) {
11440
+ const abs = resolvePath(file);
11441
+ if (!existsSync(abs)) throw new Error(`Seed file not found: ${file}`);
11442
+ return JSON.parse(readFileSync(abs, "utf8"));
11443
+ }
11444
+ async function loadSeedModule(file) {
11445
+ const abs = resolvePath(file);
11446
+ if (!existsSync(abs)) throw new Error(`Seed module not found: ${file}`);
11447
+ let mod;
11448
+ try {
11449
+ mod = await import(pathToFileURL(abs).href);
11450
+ } catch (err) {
11451
+ if (abs.endsWith(".ts") && /Unknown file extension|UNKNOWN_FILE_EXTENSION/i.test(String(err))) {
11452
+ throw new Error(
11453
+ `TypeScript seed modules need a TS-capable runtime. Run via bun (\`bunx @cimplify/sdk mock --seed-module ${file}\`), or use a .mjs/.js/.json seed.`
11454
+ );
11455
+ }
11456
+ throw err;
11457
+ }
11458
+ const seed = mod.default ?? mod.seed;
11459
+ if (typeof seed !== "function") {
11460
+ throw new Error(`Seed module ${file} must default-export a seed function (see defineSeed).`);
11461
+ }
11462
+ return seed;
11463
+ }
11464
+ function discoverConventionSeed() {
11465
+ for (const name of CONVENTION_FILES) {
11466
+ const abs = resolve(process.cwd(), name);
11467
+ if (existsSync(abs)) return abs;
11468
+ }
11469
+ return null;
11470
+ }
11471
+ async function resolveSeedSource(opts) {
11472
+ if (opts.seedFile) {
11473
+ return { source: { kind: "json", data: readSnapshot(opts.seedFile) }, label: `file: ${opts.seedFile}` };
11474
+ }
11475
+ if (opts.seedModule) {
11476
+ return { source: { kind: "fn", seed: await loadSeedModule(opts.seedModule) }, label: `module: ${opts.seedModule}` };
11477
+ }
11478
+ if (!opts.seedExplicit) {
11479
+ const found = discoverConventionSeed();
11480
+ if (found) {
11481
+ const label = `convention: ${basename(found)}`;
11482
+ if (found.endsWith(".json")) return { source: { kind: "json", data: readSnapshot(found) }, label };
11483
+ return { source: { kind: "fn", seed: await loadSeedModule(found) }, label };
11484
+ }
11485
+ }
11486
+ return { source: opts.seed, label: opts.seed };
11487
+ }
11205
11488
  async function main() {
11206
11489
  const opts = parseArgs(process.argv.slice(2));
11490
+ const { source, label } = await resolveSeedSource(opts);
11207
11491
  const handle = createMockApp({
11208
- seed: opts.seed,
11492
+ seed: source,
11209
11493
  authMode: opts.authMode,
11210
11494
  defaultOtp: opts.defaultOtp,
11211
11495
  rngSeed: opts.rngSeed,
11212
11496
  cors: opts.cors,
11213
11497
  frozenAt: opts.frozenAt
11214
11498
  });
11499
+ if (opts.dump) {
11500
+ const out = resolvePath(opts.dump);
11501
+ persistToFile(handle.deps.registry, out);
11502
+ console.log(`dumped ${label} \u2192 ${opts.dump} (business: ${handle.deps.defaultBusinessId})`);
11503
+ return;
11504
+ }
11215
11505
  if (opts.persist) {
11216
11506
  const loaded = loadFromFile(handle.deps.registry, opts.persist);
11217
11507
  autoPersist(handle.deps.registry, opts.persist);
@@ -11232,7 +11522,7 @@ async function main() {
11232
11522
  }
11233
11523
  serve({ fetch: handle.app.fetch, port: opts.port, hostname: opts.host }, () => {
11234
11524
  console.log(`@cimplify/sdk/mock listening on http://${opts.host}:${opts.port}`);
11235
- console.log(` seed: ${opts.seed} (business: ${handle.deps.defaultBusinessId})`);
11525
+ console.log(` seed: ${label} (business: ${handle.deps.defaultBusinessId})`);
11236
11526
  console.log(` state: ${opts.persist ? `persisted at ${opts.persist}` : "in-memory"}`);
11237
11527
  console.log(` admin: http://${opts.host}:${opts.port}/_mock`);
11238
11528
  console.log(` events: http://${opts.host}:${opts.port}/_mock/events/stream (SSE)`);
@@ -11241,6 +11531,6 @@ async function main() {
11241
11531
  });
11242
11532
  }
11243
11533
  main().catch((err) => {
11244
- console.error("Failed to start:", err);
11534
+ console.error("Failed to start:", err instanceof Error ? err.message : err);
11245
11535
  process.exit(1);
11246
11536
  });
@@ -5901,6 +5901,14 @@ function createLinkService({ clock, ids }) {
5901
5901
  };
5902
5902
  }
5903
5903
 
5904
+ // src/mock/state/snapshot.ts
5905
+ function applySnapshot(reg, data) {
5906
+ for (const [k, v] of Object.entries(data)) {
5907
+ const store = reg[k];
5908
+ if (store) store.loadJSON(v);
5909
+ }
5910
+ }
5911
+
5904
5912
  // src/mock/seeds/image.ts
5905
5913
  var CDN_BASE_URL = "https://static-tmp.cimplify.io";
5906
5914
  function seedImage(industry, slug) {
@@ -10268,15 +10276,217 @@ function applySeed(registry, name) {
10268
10276
  return fn(registry);
10269
10277
  }
10270
10278
 
10279
+ // src/mock/seeds/context.ts
10280
+ var SEED_ISO = "2026-01-01T00:00:00.000Z";
10281
+ var ID_PREFIX = {
10282
+ business: "bus_",
10283
+ category: "cat_",
10284
+ collection: "col_",
10285
+ product: "prod_",
10286
+ service: "svc_"
10287
+ };
10288
+ var DEFAULT_CURRENCY = "GHS";
10289
+ function slugify(value) {
10290
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
10291
+ }
10292
+ function makeSeedContext(registry) {
10293
+ let current;
10294
+ const requireBusiness = () => {
10295
+ if (current) return current;
10296
+ const sole = registry.businesses.list();
10297
+ if (sole.length === 1) {
10298
+ current = sole[0];
10299
+ return current;
10300
+ }
10301
+ throw new Error("Seed must create a business (ctx.business) before adding child records.");
10302
+ };
10303
+ const resolveCategoryId = (cat) => {
10304
+ if (!cat) return void 0;
10305
+ if (typeof cat !== "string") return cat.id;
10306
+ if (registry.categories.get(cat)) return cat;
10307
+ return registry.categories.list().find((c) => c.slug === cat)?.id;
10308
+ };
10309
+ const resolveCollectionId = (col) => {
10310
+ if (!col) return void 0;
10311
+ if (typeof col !== "string") return col.id;
10312
+ if (registry.collections.get(col)) return col;
10313
+ return registry.collections.list().find((c) => c.slug === col)?.id;
10314
+ };
10315
+ return {
10316
+ registry,
10317
+ business(input) {
10318
+ const handle = input.handle ?? slugify(input.name);
10319
+ const id = `${ID_PREFIX.business}${slugify(handle)}`;
10320
+ const business = {
10321
+ business_type: "other",
10322
+ email: `hello@${handle}.test`,
10323
+ default_currency: DEFAULT_CURRENCY,
10324
+ default_offers_table_service: false,
10325
+ default_accepts_online_orders: true,
10326
+ status: "active",
10327
+ created_by: "seed",
10328
+ preferences: {},
10329
+ is_online_only: true,
10330
+ enabled_payment_types: ["mobile_money"],
10331
+ default_location_settings: {},
10332
+ country_code: "GH",
10333
+ timezone: "Africa/Accra",
10334
+ ...input,
10335
+ id,
10336
+ handle,
10337
+ created_at: input.created_at ?? SEED_ISO,
10338
+ updated_at: input.updated_at ?? SEED_ISO
10339
+ };
10340
+ registry.businesses.put(id, business);
10341
+ current = business;
10342
+ return business;
10343
+ },
10344
+ category(input) {
10345
+ const business = requireBusiness();
10346
+ const slug = input.slug ?? slugify(input.name);
10347
+ const id = `${ID_PREFIX.category}${slug}`;
10348
+ const category = {
10349
+ id,
10350
+ business_id: business.id,
10351
+ name: input.name,
10352
+ slug,
10353
+ description: input.description,
10354
+ image: input.image,
10355
+ product_ids: [],
10356
+ display_order: input.displayOrder ?? 0,
10357
+ is_active: true,
10358
+ created_at: SEED_ISO,
10359
+ updated_at: SEED_ISO
10360
+ };
10361
+ registry.categories.put(id, category);
10362
+ return category;
10363
+ },
10364
+ collection(input) {
10365
+ const business = requireBusiness();
10366
+ const slug = input.slug ?? slugify(input.name);
10367
+ const id = `${ID_PREFIX.collection}${slug}`;
10368
+ const collection = {
10369
+ id,
10370
+ business_id: business.id,
10371
+ name: input.name,
10372
+ slug,
10373
+ description: input.description,
10374
+ product_ids: [],
10375
+ is_active: true,
10376
+ created_at: SEED_ISO,
10377
+ updated_at: SEED_ISO
10378
+ };
10379
+ registry.collections.put(id, collection);
10380
+ return collection;
10381
+ },
10382
+ product(input) {
10383
+ const business = requireBusiness();
10384
+ const slug = input.slug ?? slugify(input.name);
10385
+ const id = `${ID_PREFIX.product}${slug}`;
10386
+ const categoryId = resolveCategoryId(input.category);
10387
+ const collectionId = resolveCollectionId(input.collection);
10388
+ const product = {
10389
+ id,
10390
+ business_id: business.id,
10391
+ name: input.name,
10392
+ slug,
10393
+ description: input.description,
10394
+ product_type: input.productType ?? "product",
10395
+ base_price: input.price,
10396
+ currency: input.currency ?? business.default_currency,
10397
+ image: input.image,
10398
+ images: input.images ?? (input.image ? [input.image] : []),
10399
+ is_available: true,
10400
+ category_ids: categoryId ? [categoryId] : [],
10401
+ collection_ids: collectionId ? [collectionId] : [],
10402
+ add_on_ids: [],
10403
+ variant_ids: [],
10404
+ tags: input.tags ?? [],
10405
+ calories: input.calories,
10406
+ allergies: input.allergies ?? [],
10407
+ ingredients: input.ingredients ?? [],
10408
+ pairings: [],
10409
+ is_signature: input.isSignature ?? false,
10410
+ is_new: input.isNew ?? false,
10411
+ duration_minutes: input.durationMinutes,
10412
+ metadata: input.metadata,
10413
+ created_at: SEED_ISO,
10414
+ updated_at: SEED_ISO
10415
+ };
10416
+ registry.products.put(id, product);
10417
+ if (categoryId) {
10418
+ const cat = registry.categories.get(categoryId);
10419
+ if (cat && !cat.product_ids.includes(id)) cat.product_ids.push(id);
10420
+ }
10421
+ if (collectionId) {
10422
+ const col = registry.collections.get(collectionId);
10423
+ if (col && !col.product_ids.includes(id)) col.product_ids.push(id);
10424
+ }
10425
+ return product;
10426
+ },
10427
+ service(input) {
10428
+ const business = requireBusiness();
10429
+ const id = `${ID_PREFIX.service}${slugify(input.name)}`;
10430
+ const service = {
10431
+ id,
10432
+ business_id: business.id,
10433
+ name: input.name,
10434
+ description: input.description,
10435
+ duration_minutes: input.durationMinutes,
10436
+ price: input.price,
10437
+ is_available: true
10438
+ };
10439
+ registry.services.put(id, service);
10440
+ return service;
10441
+ },
10442
+ image: seedImage,
10443
+ extend(name) {
10444
+ const result = applySeed(registry, name);
10445
+ current = registry.businesses.get(result.businessId) ?? current;
10446
+ return result;
10447
+ }
10448
+ };
10449
+ }
10450
+ function defineSeed(seed) {
10451
+ return seed;
10452
+ }
10453
+
10454
+ // src/mock/seeds/source.ts
10455
+ var SNAPSHOT_META_KEY = "__meta";
10456
+ function loadSnapshot(registry, data, businessId) {
10457
+ applySnapshot(registry, data);
10458
+ if (businessId) return { businessId };
10459
+ const meta = data[SNAPSHOT_META_KEY];
10460
+ if (meta?.defaultBusinessId) return { businessId: meta.defaultBusinessId };
10461
+ const businesses = registry.businesses.list();
10462
+ if (businesses.length === 1) return { businessId: businesses[0].id };
10463
+ if (businesses.length === 0) {
10464
+ throw new Error("Seed snapshot has no business. Add one, or set __meta.defaultBusinessId.");
10465
+ }
10466
+ throw new Error(
10467
+ `Seed snapshot has ${businesses.length} businesses; set __meta.defaultBusinessId to pick one.`
10468
+ );
10469
+ }
10470
+ function resolveSeed(registry, source) {
10471
+ if (typeof source === "string") return applySeed(registry, source);
10472
+ switch (source.kind) {
10473
+ case "builtin":
10474
+ return applySeed(registry, source.name);
10475
+ case "json":
10476
+ return loadSnapshot(registry, source.data, source.businessId);
10477
+ case "fn":
10478
+ return source.seed(makeSeedContext(registry));
10479
+ }
10480
+ }
10481
+
10271
10482
  // src/mock/deps.ts
10272
10483
  function createDeps(options = {}) {
10273
10484
  const clock = createClock({ frozenAt: options.frozenAt });
10274
10485
  const ids = createIdGen(options.rngSeed);
10275
10486
  const bus = createBus({ idGen: () => ids.raw("evt"), clock: () => clock.now() });
10276
10487
  const registry = createStateRegistry();
10277
- const seedName = options.seed ?? "default";
10278
- const seedResult = applySeed(registry, seedName);
10279
- let defaultBusinessId = seedResult.businessId;
10488
+ const seedSource = options.seed ?? "default";
10489
+ let defaultBusinessId = resolveSeed(registry, seedSource).businessId;
10280
10490
  const auth = createAuthService({
10281
10491
  registry,
10282
10492
  clock,
@@ -10330,11 +10540,10 @@ function createDeps(options = {}) {
10330
10540
  return defaultBusinessId;
10331
10541
  },
10332
10542
  chaos,
10333
- resetAll(newSeed) {
10543
+ resetAll(next) {
10334
10544
  for (const store of Object.values(registry)) store.reset();
10335
10545
  bus.clear();
10336
- const r = applySeed(registry, newSeed ?? seedName);
10337
- defaultBusinessId = r.businessId;
10546
+ defaultBusinessId = resolveSeed(registry, next ?? seedSource).businessId;
10338
10547
  return defaultBusinessId;
10339
10548
  }
10340
10549
  };
@@ -10437,9 +10646,17 @@ function setupWebhooks(bus, opts) {
10437
10646
  });
10438
10647
  }
10439
10648
 
10649
+ exports.SEED_ISO = SEED_ISO;
10650
+ exports.SNAPSHOT_META_KEY = SNAPSHOT_META_KEY;
10440
10651
  exports.applySeed = applySeed;
10652
+ exports.applySnapshot = applySnapshot;
10441
10653
  exports.createDeps = createDeps;
10442
10654
  exports.createMockApp = createMockApp;
10655
+ exports.defineSeed = defineSeed;
10656
+ exports.loadSnapshot = loadSnapshot;
10657
+ exports.makeSeedContext = makeSeedContext;
10658
+ exports.resolveSeed = resolveSeed;
10443
10659
  exports.setupWebhooks = setupWebhooks;
10444
10660
  exports.signPayload = signPayload;
10661
+ exports.slugify = slugify;
10445
10662
  exports.verifySignature = verifySignature;