@jaypie/mcp 0.2.5 → 0.2.7

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.
@@ -359,3 +359,259 @@ class TenantFabricator {
359
359
  4. **Seed Flexibility**: Accepts strings, numbers, or UUIDs as seeds
360
360
  5. **Hierarchical Generation**: Nested fabricators enable deterministic tree structures for complex data models
361
361
  6. **Identity & Naming**: Every fabricator has a unique ID and name for tracking and debugging
362
+
363
+ ## EventFabricator: Temporal Event Generation
364
+
365
+ ### Overview
366
+
367
+ `EventFabricator` is an abstract base class for generating temporally-distributed events across a year. It builds on `Fabricator` to add:
368
+ - Annual event counts with configurable distribution
369
+ - Temporal templates for hour, day, week, month, and date weighting
370
+ - Timezone-aware hour shifting
371
+ - Derived event system for cascading events
372
+
373
+ ### Basic Usage
374
+
375
+ ```typescript
376
+ import {
377
+ EventFabricator,
378
+ HOURS_BUSINESS,
379
+ DAYS_WEEKDAYS_ONLY,
380
+ type CreateEventParams,
381
+ } from "@jaypie/fabricator";
382
+
383
+ interface SimpleEvent {
384
+ id: string;
385
+ timestamp: Date;
386
+ }
387
+
388
+ class SimpleEventFabricator extends EventFabricator<SimpleEvent> {
389
+ protected createEvent({ seed, timestamp }: CreateEventParams): SimpleEvent {
390
+ return { id: seed, timestamp };
391
+ }
392
+ }
393
+
394
+ const fab = new SimpleEventFabricator({
395
+ seed: "my-events",
396
+ annualCount: 1000,
397
+ template: [HOURS_BUSINESS, DAYS_WEEKDAYS_ONLY],
398
+ });
399
+
400
+ const events = fab.events({ year: 2025 });
401
+ // Returns 1000 events during business hours on weekdays
402
+ ```
403
+
404
+ ### Temporal Templates
405
+
406
+ Templates control event distribution. They can be combined:
407
+
408
+ **Hour Templates:**
409
+ - `HOURS_BUSINESS` - 8am-5pm
410
+ - `HOURS_RETAIL` - 10am-8pm
411
+ - `HOURS_EVENING` - 6pm-10pm
412
+ - `HOURS_24_7` - All hours equal
413
+
414
+ **Day Templates:**
415
+ - `DAYS_WEEKDAYS_ONLY` - Mon-Fri only
416
+ - `DAYS_NO_SUNDAY` - All but Sunday
417
+ - `DAYS_NO_MONDAY` - All but Monday
418
+
419
+ **Curve Templates (gradual peaks):**
420
+ - `CURVE_EVENING_PEAK` - Peaks at 7pm
421
+ - `CURVE_ECOMMERCE` - Peaks at 8pm
422
+ - `CURVE_MIDDAY_PEAK` - Peaks at 11am
423
+
424
+ **Spike Templates (sharp peaks):**
425
+ - `SPIKE_MORNING` - 7-8am peak
426
+ - `SPIKE_LUNCH` - 12-1pm peak
427
+ - `SPIKE_EVENING` - 6-7pm peak
428
+
429
+ **Boost/Lull Templates (multipliers):**
430
+ - `BOOST_SUMMER`, `LULL_SUMMER`
431
+ - `BOOST_WINTER`, `LULL_WINTER`
432
+ - `BOOST_WEEKENDS`, `LULL_WEEKENDS`
433
+ - `BOOST_HOLIDAY_SEASON` - Nov-Dec 1.5x
434
+
435
+ ```typescript
436
+ const fab = new MyFabricator({
437
+ template: [
438
+ HOURS_BUSINESS,
439
+ DAYS_WEEKDAYS_ONLY,
440
+ BOOST_HOLIDAY_SEASON,
441
+ CURVE_MIDDAY_PEAK,
442
+ ],
443
+ });
444
+ ```
445
+
446
+ ## Derived Events: Cascading Event Generation
447
+
448
+ ### Overview
449
+
450
+ The derived event system allows events to spawn follow-up events based on probabilistic rules. This is ideal for modeling:
451
+ - Financial transactions with voids, refunds, chargebacks
452
+ - Subscription renewals with payment retries
453
+ - Any event chains with cause-and-effect relationships
454
+
455
+ ### Configuration
456
+
457
+ ```typescript
458
+ import {
459
+ EventFabricator,
460
+ CHANCE,
461
+ type DerivedConfig,
462
+ type TimestampedEvent,
463
+ } from "@jaypie/fabricator";
464
+
465
+ interface Transaction extends TimestampedEvent {
466
+ id: string;
467
+ amount: number;
468
+ type: "purchase" | "void" | "refund" | "chargeback";
469
+ parentId?: string;
470
+ timestamp: Date;
471
+ }
472
+
473
+ const derivedConfig: DerivedConfig<Transaction> = {
474
+ rules: [
475
+ {
476
+ name: "void",
477
+ probability: CHANCE.RARE, // 2.1%
478
+ condition: (parent) => parent.type === "purchase",
479
+ timing: { mode: "same-day" },
480
+ createDerived: ({ parent, seed, timestamp }) => ({
481
+ id: seed,
482
+ type: "void",
483
+ amount: -parent.amount,
484
+ timestamp,
485
+ parentId: parent.id,
486
+ }),
487
+ },
488
+ {
489
+ name: "refund",
490
+ probability: 0.10, // 10%
491
+ timing: {
492
+ mode: "range",
493
+ delayMin: 1,
494
+ delayMax: 14,
495
+ unit: "days",
496
+ },
497
+ createDerived: ({ parent, seed, timestamp }) => ({
498
+ id: seed,
499
+ type: "refund",
500
+ amount: -parent.amount,
501
+ timestamp,
502
+ parentId: parent.id,
503
+ }),
504
+ },
505
+ ],
506
+ boundaryBehavior: "include", // include | exclude | clamp
507
+ maxDepth: 4, // Prevent infinite chains
508
+ };
509
+ ```
510
+
511
+ ### Timing Modes
512
+
513
+ ```typescript
514
+ interface DerivedTiming {
515
+ mode: "same-day" | "fixed" | "range" | "recurring";
516
+ delayMin?: number;
517
+ delayMax?: number;
518
+ unit?: "days" | "weeks" | "months" | "hours" | "minutes" | "seconds";
519
+ interval?: number; // For recurring
520
+ maxRecurrences?: number; // For recurring
521
+ until?: Date | "end-of-year";
522
+ }
523
+ ```
524
+
525
+ **Examples:**
526
+ - Same day: `{ mode: "same-day" }`
527
+ - Fixed delay: `{ mode: "fixed", delayMin: 7, unit: "days" }`
528
+ - Range: `{ mode: "range", delayMin: 1, delayMax: 14, unit: "days" }`
529
+ - Monthly recurring: `{ mode: "recurring", interval: 1, unit: "months", until: "end-of-year" }`
530
+
531
+ ### Nested Derived Events (Chains)
532
+
533
+ Derived events can spawn their own derived events:
534
+
535
+ ```typescript
536
+ {
537
+ name: "chargeback",
538
+ probability: CHANCE.RARE,
539
+ timing: { mode: "range", delayMin: 30, delayMax: 90, unit: "days" },
540
+ createDerived: ({ parent, seed, timestamp }) => ({
541
+ id: seed,
542
+ type: "chargeback",
543
+ amount: -parent.amount,
544
+ timestamp,
545
+ parentId: parent.id,
546
+ }),
547
+ derived: [ // Nested rules for chargebacks
548
+ {
549
+ name: "representment",
550
+ probability: 0.70, // 70% of chargebacks
551
+ timing: { mode: "range", delayMin: 7, delayMax: 21, unit: "days" },
552
+ createDerived: ({ parent, seed, timestamp }) => ({
553
+ id: seed,
554
+ type: "representment",
555
+ amount: Math.abs(parent.amount), // Re-charge
556
+ timestamp,
557
+ parentId: parent.id,
558
+ }),
559
+ },
560
+ ],
561
+ }
562
+ ```
563
+
564
+ ### Using with EventFabricator
565
+
566
+ ```typescript
567
+ class TransactionFabricator extends EventFabricator<Transaction> {
568
+ constructor(options = {}) {
569
+ super({
570
+ ...options,
571
+ derived: derivedConfig,
572
+ template: [HOURS_BUSINESS, DAYS_WEEKDAYS_ONLY],
573
+ });
574
+ }
575
+
576
+ protected createEvent({ seed, timestamp }: CreateEventParams): Transaction {
577
+ const fab = new Fabricator({ seed });
578
+ return {
579
+ id: seed,
580
+ type: "purchase",
581
+ amount: fab.random({ min: 10, max: 500, currency: true }),
582
+ timestamp,
583
+ };
584
+ }
585
+ }
586
+
587
+ const fab = new TransactionFabricator({
588
+ seed: "my-business",
589
+ annualCount: 1000,
590
+ });
591
+
592
+ // Get all events including derived events
593
+ const events = fab.events({ year: 2025 });
594
+
595
+ // Get events with metadata (parent refs, depth, rule names)
596
+ const eventsWithMeta = fab.eventsWithMeta({ year: 2025 });
597
+ ```
598
+
599
+ ### Event Metadata
600
+
601
+ `eventsWithMeta()` returns events with relationship data:
602
+
603
+ ```typescript
604
+ interface EventWithDerivedMeta<T> {
605
+ event: T;
606
+ depth: number; // 0 = primary, 1 = first derived, etc.
607
+ parentSeed?: string; // Seed of parent event
608
+ ruleName?: string; // Rule that created this derived event
609
+ }
610
+ ```
611
+
612
+ ### Key Properties
613
+
614
+ 1. **Deterministic**: Same seed produces same derived event cascade
615
+ 2. **Chronological**: All events (primary + derived) sorted by timestamp
616
+ 3. **Depth-Limited**: `maxDepth` prevents infinite chains
617
+ 4. **Boundary Control**: Events outside target year can be included, excluded, or clamped