bunsane 0.2.4 → 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.
Files changed (35) hide show
  1. package/core/ArcheType.ts +67 -34
  2. package/core/BatchLoader.ts +215 -30
  3. package/core/Entity.ts +2 -2
  4. package/core/RequestContext.ts +15 -10
  5. package/core/RequestLoaders.ts +4 -2
  6. package/core/cache/CacheProvider.ts +1 -0
  7. package/core/cache/MemoryCache.ts +10 -1
  8. package/core/cache/RedisCache.ts +16 -2
  9. package/core/validateEnv.ts +8 -0
  10. package/database/DatabaseHelper.ts +113 -1
  11. package/database/index.ts +78 -45
  12. package/docs/SCALABILITY_PLAN.md +175 -0
  13. package/package.json +13 -2
  14. package/query/CTENode.ts +44 -24
  15. package/query/ComponentInclusionNode.ts +181 -91
  16. package/query/Query.ts +9 -9
  17. package/tests/benchmark/BENCHMARK_DATABASES_PLAN.md +338 -0
  18. package/tests/benchmark/bunfig.toml +9 -0
  19. package/tests/benchmark/fixtures/EcommerceComponents.ts +283 -0
  20. package/tests/benchmark/fixtures/EcommerceDataGenerators.ts +301 -0
  21. package/tests/benchmark/fixtures/RelationTracker.ts +159 -0
  22. package/tests/benchmark/fixtures/index.ts +6 -0
  23. package/tests/benchmark/index.ts +22 -0
  24. package/tests/benchmark/noop-preload.ts +3 -0
  25. package/tests/benchmark/runners/BenchmarkLoader.ts +132 -0
  26. package/tests/benchmark/runners/index.ts +4 -0
  27. package/tests/benchmark/scenarios/query-benchmarks.test.ts +465 -0
  28. package/tests/benchmark/scripts/generate-db.ts +344 -0
  29. package/tests/benchmark/scripts/run-benchmarks.ts +97 -0
  30. package/tests/integration/query/Query.complexAnalysis.test.ts +557 -0
  31. package/tests/integration/query/Query.explainAnalyze.test.ts +233 -0
  32. package/tests/stress/fixtures/RealisticComponents.ts +235 -0
  33. package/tests/stress/scenarios/realistic-scenarios.test.ts +1081 -0
  34. package/tests/stress/scenarios/timeout-investigation.test.ts +522 -0
  35. package/tests/unit/BatchLoader.test.ts +139 -25
@@ -0,0 +1,338 @@
1
+ # PGlite Persistent Benchmark Databases Plan
2
+
3
+ ## Overview
4
+
5
+ Create pre-populated PGlite databases at various scale tiers for consistent, reproducible query benchmarks without re-seeding overhead.
6
+
7
+ ## Database Tiers
8
+
9
+ | Tier | Entities | Products | Orders | Users | Reviews | Est. Size |
10
+ |------|----------|----------|--------|-------|---------|-----------|
11
+ | xs | 10,000 | 5,000 | 3,000 | 1,000 | 1,000 | ~50 MB |
12
+ | sm | 50,000 | 25,000 | 15,000 | 5,000 | 5,000 | ~250 MB |
13
+ | md | 100,000 | 50,000 | 30,000 | 10,000| 10,000 | ~500 MB |
14
+ | lg | 500,000 | 250,000 | 150,000| 50,000| 50,000 | ~2.5 GB |
15
+ | xl | 1,000,000| 500,000 | 300,000| 100,000| 100,000| ~5 GB |
16
+
17
+ ## Directory Structure
18
+
19
+ ```
20
+ tests/benchmark/
21
+ ├── BENCHMARK_DATABASES_PLAN.md # This plan
22
+ ├── databases/ # Persistent PGlite databases (gitignored)
23
+ │ ├── bench-xs/ # 10k entities
24
+ │ ├── bench-sm/ # 50k entities
25
+ │ ├── bench-md/ # 100k entities
26
+ │ ├── bench-lg/ # 500k entities
27
+ │ └── bench-xl/ # 1M entities
28
+ ├── fixtures/
29
+ │ └── BenchmarkComponents.ts # Reusable components for benchmarks
30
+ ├── generators/
31
+ │ ├── index.ts # Main generator entry point
32
+ │ ├── DataGenerator.ts # Realistic data generation utilities
33
+ │ ├── EntitySeeder.ts # Batch entity creation with relations
34
+ │ └── ProgressReporter.ts # CLI progress reporting
35
+ ├── runners/
36
+ │ ├── QueryBenchmark.ts # Query benchmark runner
37
+ │ └── RelationBenchmark.ts # BelongsTo/HasMany benchmark
38
+ └── generate-databases.ts # CLI script to generate all tiers
39
+ ```
40
+
41
+ ## Data Model (E-commerce Scenario)
42
+
43
+ ### Components (from RealisticComponents.ts)
44
+
45
+ 1. **Product** - Core product data (name, SKU, category, status, rating)
46
+ 2. **Inventory** - Stock tracking (quantity, warehouse, stock status)
47
+ 3. **Pricing** - Price info (base, sale, cost, discount, currency)
48
+ 4. **Vendor** - Supplier info (name, region, rating, tier)
49
+ 5. **ProductMetrics** - Analytics (views, purchases, conversion)
50
+
51
+ ### Additional Components for Relations
52
+
53
+ 6. **User** - Customer data (name, email, tier, region)
54
+ 7. **Order** - Order data (userId, status, total, itemCount)
55
+ 8. **OrderItem** - Line items (orderId, productId, quantity, price)
56
+ 9. **Review** - Product reviews (userId, productId, rating, text)
57
+ 10. **Wishlist** - User wishlists (userId, productIds[])
58
+
59
+ ### Relation Structure
60
+
61
+ ```
62
+ User (1) ──HasMany──> Order (N)
63
+ User (1) ──HasMany──> Review (N)
64
+ User (1) ──HasMany──> Wishlist (1)
65
+
66
+ Order (1) ──HasMany──> OrderItem (N)
67
+ Order (N) ──BelongsTo──> User (1)
68
+
69
+ OrderItem (N) ──BelongsTo──> Order (1)
70
+ OrderItem (N) ──BelongsTo──> Product (1)
71
+
72
+ Review (N) ──BelongsTo──> User (1)
73
+ Review (N) ──BelongsTo──> Product (1)
74
+
75
+ Product (1) ──HasMany──> Review (N)
76
+ Product (1) ──HasMany──> OrderItem (N)
77
+ Product (N) ──BelongsTo──> Vendor (1)
78
+ ```
79
+
80
+ ## Implementation Steps
81
+
82
+ ### Phase 1: Infrastructure Setup
83
+
84
+ 1. **Create directory structure**
85
+ - Create `tests/benchmark/` directories
86
+ - Add `databases/` to `.gitignore`
87
+
88
+ 2. **Create BenchmarkComponents.ts**
89
+ - Define User, Order, OrderItem, Review, Wishlist components
90
+ - Define BenchmarkArchetypes with proper relations
91
+
92
+ 3. **Create DataGenerator.ts**
93
+ - Faker-like utilities for realistic data (names, emails, addresses)
94
+ - Deterministic seeding with configurable seed for reproducibility
95
+ - Batch generation for memory efficiency
96
+
97
+ ### Phase 2: Seeding System
98
+
99
+ 4. **Create EntitySeeder.ts**
100
+ ```typescript
101
+ interface SeederConfig {
102
+ tier: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
103
+ seed?: number; // For reproducibility
104
+ batchSize?: number; // Default 1000
105
+ showProgress?: boolean; // CLI progress bar
106
+ }
107
+
108
+ class EntitySeeder {
109
+ async seedProducts(count: number): Promise<string[]>;
110
+ async seedUsers(count: number): Promise<string[]>;
111
+ async seedOrders(userIds: string[], productIds: string[]): Promise<void>;
112
+ async seedReviews(userIds: string[], productIds: string[]): Promise<void>;
113
+ async createIndexes(): Promise<void>;
114
+ async vacuum(): Promise<void>;
115
+ }
116
+ ```
117
+
118
+ 5. **Create ProgressReporter.ts**
119
+ - CLI progress bars for long-running operations
120
+ - ETA calculation
121
+ - Memory usage monitoring
122
+
123
+ ### Phase 3: Generator Script
124
+
125
+ 6. **Create generate-databases.ts**
126
+ ```bash
127
+ # Generate all tiers
128
+ bun tests/benchmark/generate-databases.ts --all
129
+
130
+ # Generate specific tier
131
+ bun tests/benchmark/generate-databases.ts --tier=md
132
+
133
+ # Regenerate (delete existing + recreate)
134
+ bun tests/benchmark/generate-databases.ts --tier=sm --force
135
+
136
+ # Custom seed for reproducibility
137
+ bun tests/benchmark/generate-databases.ts --tier=xs --seed=42
138
+ ```
139
+
140
+ 7. **Generation Process**
141
+ ```
142
+ For each tier:
143
+ 1. Check if database directory exists (skip if exists, unless --force)
144
+ 2. Create PGlite with dataDir
145
+ 3. Run database migrations (PrepareDatabase)
146
+ 4. Register components
147
+ 5. Seed entities in batches with progress
148
+ 6. Create relation foreign keys
149
+ 7. Create indexes (CreateRelationIndexes)
150
+ 8. Run VACUUM ANALYZE
151
+ 9. Report final stats (entity count, db size, time taken)
152
+ ```
153
+
154
+ ### Phase 4: Benchmark Runner
155
+
156
+ 8. **Create benchmark loading utility**
157
+ ```typescript
158
+ // tests/benchmark/runners/loadBenchmarkDb.ts
159
+ export async function loadBenchmarkDatabase(
160
+ tier: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
161
+ ): Promise<{ pg: PGlite; server: PGLiteSocketServer; cleanup: () => Promise<void> }>;
162
+ ```
163
+
164
+ 9. **Create QueryBenchmark.ts**
165
+ ```typescript
166
+ interface BenchmarkResult {
167
+ name: string;
168
+ tier: string;
169
+ entityCount: number;
170
+ queryTimeMs: number;
171
+ rowsReturned: number;
172
+ memoryUsedMB: number;
173
+ }
174
+
175
+ // Benchmark scenarios
176
+ - Simple filter (single component, indexed field)
177
+ - Multi-component filter (2-3 components with AND)
178
+ - Complex query (multi-component + sort + limit)
179
+ - BelongsTo resolution (N users with their orders)
180
+ - HasMany resolution (N products with their reviews)
181
+ - Deep nesting (Order -> User -> Reviews -> Products)
182
+ ```
183
+
184
+ ### Phase 5: npm Scripts
185
+
186
+ 10. **Add to package.json**
187
+ ```json
188
+ {
189
+ "scripts": {
190
+ "bench:generate": "bun tests/benchmark/generate-databases.ts --all",
191
+ "bench:generate:xs": "bun tests/benchmark/generate-databases.ts --tier=xs",
192
+ "bench:generate:sm": "bun tests/benchmark/generate-databases.ts --tier=sm",
193
+ "bench:generate:md": "bun tests/benchmark/generate-databases.ts --tier=md",
194
+ "bench:generate:lg": "bun tests/benchmark/generate-databases.ts --tier=lg",
195
+ "bench:generate:xl": "bun tests/benchmark/generate-databases.ts --tier=xl",
196
+ "bench:query": "bun tests/benchmark/runners/QueryBenchmark.ts",
197
+ "bench:relations": "bun tests/benchmark/runners/RelationBenchmark.ts"
198
+ }
199
+ }
200
+ ```
201
+
202
+ ## Data Generation Details
203
+
204
+ ### Realistic Distribution
205
+
206
+ ```typescript
207
+ // Product categories follow power-law distribution
208
+ // ~40% in top 3 categories, ~60% in remaining 7
209
+ const categoryWeights = {
210
+ 'Electronics': 0.20,
211
+ 'Clothing': 0.12,
212
+ 'Home & Garden': 0.08,
213
+ // ... rest evenly distributed
214
+ };
215
+
216
+ // Order counts per user follow exponential distribution
217
+ // Most users: 1-5 orders, some power users: 50+ orders
218
+ function getOrderCountForUser(): number {
219
+ const r = Math.random();
220
+ if (r < 0.7) return randomInt(1, 5);
221
+ if (r < 0.9) return randomInt(6, 20);
222
+ return randomInt(21, 100);
223
+ }
224
+
225
+ // Reviews are skewed toward extreme ratings (1, 4, 5 stars)
226
+ function getReviewRating(): number {
227
+ const r = Math.random();
228
+ if (r < 0.05) return 1; // 5% - 1 star
229
+ if (r < 0.10) return 2; // 5% - 2 stars
230
+ if (r < 0.20) return 3; // 10% - 3 stars
231
+ if (r < 0.50) return 4; // 30% - 4 stars
232
+ return 5; // 50% - 5 stars
233
+ }
234
+ ```
235
+
236
+ ### Batch Insertion Strategy
237
+
238
+ ```typescript
239
+ // Avoid memory issues with large datasets
240
+ const BATCH_SIZE = 1000;
241
+
242
+ async function seedInBatches<T>(
243
+ total: number,
244
+ generator: (batchIndex: number, batchSize: number) => Promise<T[]>,
245
+ inserter: (items: T[]) => Promise<void>
246
+ ) {
247
+ const batches = Math.ceil(total / BATCH_SIZE);
248
+ for (let i = 0; i < batches; i++) {
249
+ const items = await generator(i, Math.min(BATCH_SIZE, total - i * BATCH_SIZE));
250
+ await inserter(items);
251
+ // Allow GC between batches
252
+ if (i % 10 === 0) await Bun.sleep(1);
253
+ }
254
+ }
255
+ ```
256
+
257
+ ## Benchmark Scenarios
258
+
259
+ ### Query Performance Matrix
260
+
261
+ | Scenario | Description | Expected Behavior |
262
+ |----------|-------------|-------------------|
263
+ | Q1 | Filter by indexed field | O(log n), <50ms at 1M |
264
+ | Q2 | Filter by non-indexed field | O(n), slower |
265
+ | Q3 | Multi-component AND | Uses INTERSECT optimization |
266
+ | Q4 | Multi-component + Sort | Uses scalar subquery sort |
267
+ | Q5 | BelongsTo single | DataLoader batched |
268
+ | Q6 | HasMany collection | Single filtered query |
269
+ | Q7 | Nested relations 3-deep | DataLoader + batching |
270
+ | Q8 | Pagination (offset) | Cursor recommended at scale |
271
+ | Q9 | Count aggregation | Fast with proper indexes |
272
+ | Q10 | Full table scan | Baseline comparison |
273
+
274
+ ### Expected Performance Targets
275
+
276
+ | Tier | Q1 (indexed) | Q3 (multi) | Q5 (BelongsTo) | Q7 (nested) |
277
+ |------|--------------|------------|----------------|-------------|
278
+ | xs | <10ms | <20ms | <30ms | <50ms |
279
+ | sm | <15ms | <40ms | <50ms | <100ms |
280
+ | md | <20ms | <60ms | <80ms | <150ms |
281
+ | lg | <30ms | <100ms | <150ms | <300ms |
282
+ | xl | <50ms | <150ms | <250ms | <500ms |
283
+
284
+ ## Files to Create
285
+
286
+ 1. `tests/benchmark/fixtures/BenchmarkComponents.ts`
287
+ 2. `tests/benchmark/fixtures/BenchmarkArchetypes.ts`
288
+ 3. `tests/benchmark/generators/DataGenerator.ts`
289
+ 4. `tests/benchmark/generators/EntitySeeder.ts`
290
+ 5. `tests/benchmark/generators/ProgressReporter.ts`
291
+ 6. `tests/benchmark/generators/index.ts`
292
+ 7. `tests/benchmark/generate-databases.ts`
293
+ 8. `tests/benchmark/runners/loadBenchmarkDb.ts`
294
+ 9. `tests/benchmark/runners/QueryBenchmark.ts`
295
+ 10. `tests/benchmark/runners/RelationBenchmark.ts`
296
+
297
+ ## Gitignore Addition
298
+
299
+ ```gitignore
300
+ # Benchmark databases (large, generated locally)
301
+ tests/benchmark/databases/
302
+ ```
303
+
304
+ ## Usage Example
305
+
306
+ ```bash
307
+ # First time: Generate benchmark databases
308
+ bun run bench:generate:xs # Quick: ~30s
309
+ bun run bench:generate:md # Medium: ~5min
310
+ bun run bench:generate:xl # Large: ~30min
311
+
312
+ # Run benchmarks (uses pre-generated databases)
313
+ bun run bench:query --tier=md
314
+ bun run bench:relations --tier=lg
315
+
316
+ # Results output
317
+ # ┌─────────────────────────────────────────────────────────┐
318
+ # │ Query Benchmark Results - Tier: md (100,000 entities) │
319
+ # ├──────────────────────┬─────────┬──────────┬────────────┤
320
+ # │ Scenario │ Time │ Rows │ Memory │
321
+ # ├──────────────────────┼─────────┼──────────┼────────────┤
322
+ # │ Q1: Indexed filter │ 18ms │ 1,234 │ 2.3 MB │
323
+ # │ Q3: Multi-component │ 52ms │ 456 │ 4.1 MB │
324
+ # │ Q5: BelongsTo batch │ 71ms │ 100 │ 5.8 MB │
325
+ # │ Q7: Nested 3-deep │ 142ms │ 50 │ 12.4 MB │
326
+ # └──────────────────────┴─────────┴──────────┴────────────┘
327
+ ```
328
+
329
+ ## Next Steps
330
+
331
+ 1. Review and approve this plan
332
+ 2. Create the directory structure
333
+ 3. Implement Phase 1-2 (components + seeder)
334
+ 4. Implement Phase 3 (generator script)
335
+ 5. Generate xs/sm tiers for initial testing
336
+ 6. Implement Phase 4 (benchmark runners)
337
+ 7. Generate remaining tiers
338
+ 8. Document results and establish baselines
@@ -0,0 +1,9 @@
1
+ [test]
2
+ # No preload for benchmark tests - we manage the database connection ourselves
3
+ preload = []
4
+
5
+ # Longer timeout for benchmarks
6
+ timeout = 300000
7
+
8
+ # Single-threaded to avoid connection conflicts
9
+ smol = true
@@ -0,0 +1,283 @@
1
+ /**
2
+ * E-commerce Components for Benchmark Testing
3
+ *
4
+ * Defines benchmark-specific components with indexed foreign keys
5
+ * for testing query performance across relationships.
6
+ */
7
+ import { BaseComponent } from '../../../core/components/BaseComponent';
8
+ import { Component, CompData } from '../../../core/components/Decorators';
9
+ import { IndexedField } from '../../../core/decorators/IndexedField';
10
+
11
+ /**
12
+ * BenchUser - User profile component
13
+ */
14
+ @Component
15
+ export class BenchUser extends BaseComponent {
16
+ @CompData({ indexed: true })
17
+ @IndexedField('btree')
18
+ email!: string;
19
+
20
+ @CompData({ indexed: true })
21
+ @IndexedField('btree')
22
+ username!: string;
23
+
24
+ @CompData()
25
+ firstName!: string;
26
+
27
+ @CompData()
28
+ lastName!: string;
29
+
30
+ @CompData({ indexed: true })
31
+ @IndexedField('btree')
32
+ status!: 'active' | 'inactive' | 'suspended';
33
+
34
+ @CompData({ indexed: true })
35
+ @IndexedField('btree')
36
+ tier!: 'free' | 'basic' | 'premium' | 'enterprise';
37
+
38
+ @CompData()
39
+ @IndexedField('btree')
40
+ region!: string;
41
+
42
+ @CompData()
43
+ @IndexedField('numeric')
44
+ orderCount!: number;
45
+
46
+ @CompData()
47
+ @IndexedField('numeric')
48
+ totalSpent!: number;
49
+
50
+ @CompData()
51
+ @IndexedField('btree', true)
52
+ createdAt!: Date;
53
+
54
+ @CompData()
55
+ @IndexedField('btree', true)
56
+ lastLoginAt!: Date | null;
57
+ }
58
+
59
+ /**
60
+ * BenchProduct - Product catalog component
61
+ */
62
+ @Component
63
+ export class BenchProduct extends BaseComponent {
64
+ @CompData({ indexed: true })
65
+ @IndexedField('btree')
66
+ sku!: string;
67
+
68
+ @CompData({ indexed: true })
69
+ @IndexedField('btree')
70
+ name!: string;
71
+
72
+ @CompData()
73
+ description!: string;
74
+
75
+ @CompData({ indexed: true })
76
+ @IndexedField('btree')
77
+ category!: string;
78
+
79
+ @CompData()
80
+ @IndexedField('btree')
81
+ subcategory!: string;
82
+
83
+ @CompData({ indexed: true })
84
+ @IndexedField('btree')
85
+ brand!: string;
86
+
87
+ @CompData()
88
+ @IndexedField('numeric')
89
+ price!: number;
90
+
91
+ @CompData()
92
+ @IndexedField('numeric')
93
+ cost!: number;
94
+
95
+ @CompData()
96
+ @IndexedField('numeric')
97
+ stock!: number;
98
+
99
+ @CompData({ indexed: true })
100
+ @IndexedField('btree')
101
+ status!: 'active' | 'inactive' | 'discontinued';
102
+
103
+ @CompData()
104
+ @IndexedField('numeric')
105
+ rating!: number;
106
+
107
+ @CompData()
108
+ @IndexedField('numeric')
109
+ reviewCount!: number;
110
+
111
+ @CompData()
112
+ @IndexedField('btree', true)
113
+ createdAt!: Date;
114
+ }
115
+
116
+ /**
117
+ * BenchOrder - Order transaction component
118
+ */
119
+ @Component
120
+ export class BenchOrder extends BaseComponent {
121
+ @CompData({ indexed: true })
122
+ @IndexedField('btree')
123
+ userId!: string; // Foreign key to BenchUser entity
124
+
125
+ @CompData({ indexed: true })
126
+ @IndexedField('btree')
127
+ orderNumber!: string;
128
+
129
+ @CompData({ indexed: true })
130
+ @IndexedField('btree')
131
+ status!: 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'refunded';
132
+
133
+ @CompData()
134
+ @IndexedField('numeric')
135
+ subtotal!: number;
136
+
137
+ @CompData()
138
+ @IndexedField('numeric')
139
+ tax!: number;
140
+
141
+ @CompData()
142
+ @IndexedField('numeric')
143
+ shipping!: number;
144
+
145
+ @CompData()
146
+ @IndexedField('numeric')
147
+ total!: number;
148
+
149
+ @CompData()
150
+ @IndexedField('numeric')
151
+ itemCount!: number;
152
+
153
+ @CompData({ indexed: true })
154
+ @IndexedField('btree')
155
+ paymentMethod!: 'card' | 'paypal' | 'bank' | 'crypto';
156
+
157
+ @CompData()
158
+ @IndexedField('btree')
159
+ shippingRegion!: string;
160
+
161
+ @CompData()
162
+ @IndexedField('btree', true)
163
+ orderedAt!: Date;
164
+
165
+ @CompData()
166
+ @IndexedField('btree', true)
167
+ shippedAt!: Date | null;
168
+
169
+ @CompData()
170
+ @IndexedField('btree', true)
171
+ deliveredAt!: Date | null;
172
+ }
173
+
174
+ /**
175
+ * BenchOrderItem - Line items in an order
176
+ */
177
+ @Component
178
+ export class BenchOrderItem extends BaseComponent {
179
+ @CompData({ indexed: true })
180
+ @IndexedField('btree')
181
+ orderId!: string; // Foreign key to BenchOrder entity
182
+
183
+ @CompData({ indexed: true })
184
+ @IndexedField('btree')
185
+ productId!: string; // Foreign key to BenchProduct entity
186
+
187
+ @CompData({ indexed: true })
188
+ @IndexedField('btree')
189
+ userId!: string; // Denormalized for faster user order queries
190
+
191
+ @CompData()
192
+ @IndexedField('numeric')
193
+ quantity!: number;
194
+
195
+ @CompData()
196
+ @IndexedField('numeric')
197
+ unitPrice!: number;
198
+
199
+ @CompData()
200
+ @IndexedField('numeric')
201
+ discount!: number;
202
+
203
+ @CompData()
204
+ @IndexedField('numeric')
205
+ total!: number;
206
+
207
+ @CompData()
208
+ @IndexedField('btree')
209
+ status!: 'pending' | 'fulfilled' | 'returned' | 'refunded';
210
+ }
211
+
212
+ /**
213
+ * BenchReview - Product review component
214
+ */
215
+ @Component
216
+ export class BenchReview extends BaseComponent {
217
+ @CompData({ indexed: true })
218
+ @IndexedField('btree')
219
+ productId!: string; // Foreign key to BenchProduct entity
220
+
221
+ @CompData({ indexed: true })
222
+ @IndexedField('btree')
223
+ userId!: string; // Foreign key to BenchUser entity
224
+
225
+ @CompData()
226
+ @IndexedField('numeric')
227
+ rating!: number; // 1-5
228
+
229
+ @CompData()
230
+ title!: string;
231
+
232
+ @CompData()
233
+ content!: string;
234
+
235
+ @CompData({ indexed: true })
236
+ @IndexedField('gin')
237
+ verified!: boolean;
238
+
239
+ @CompData()
240
+ @IndexedField('numeric')
241
+ helpfulCount!: number;
242
+
243
+ @CompData()
244
+ @IndexedField('btree', true)
245
+ createdAt!: Date;
246
+ }
247
+
248
+ // Constants for data generation
249
+ export const USER_TIERS = ['free', 'basic', 'premium', 'enterprise'] as const;
250
+ export const USER_STATUSES = ['active', 'inactive', 'suspended'] as const;
251
+ export const PRODUCT_STATUSES = ['active', 'inactive', 'discontinued'] as const;
252
+ export const ORDER_STATUSES = ['pending', 'processing', 'shipped', 'delivered', 'cancelled', 'refunded'] as const;
253
+ export const ORDER_ITEM_STATUSES = ['pending', 'fulfilled', 'returned', 'refunded'] as const;
254
+ export const PAYMENT_METHODS = ['card', 'paypal', 'bank', 'crypto'] as const;
255
+
256
+ export const REGIONS = [
257
+ 'North America', 'Europe', 'Asia Pacific', 'Latin America',
258
+ 'Middle East', 'Africa', 'Oceania'
259
+ ];
260
+
261
+ export const CATEGORIES = [
262
+ 'Electronics', 'Clothing', 'Home & Garden', 'Sports', 'Books',
263
+ 'Toys', 'Beauty', 'Automotive', 'Food', 'Health'
264
+ ];
265
+
266
+ export const SUBCATEGORIES: Record<string, string[]> = {
267
+ 'Electronics': ['Smartphones', 'Laptops', 'Tablets', 'Accessories', 'Audio', 'Cameras'],
268
+ 'Clothing': ['Men', 'Women', 'Kids', 'Shoes', 'Accessories', 'Athletic'],
269
+ 'Home & Garden': ['Furniture', 'Decor', 'Kitchen', 'Garden', 'Bedding', 'Lighting'],
270
+ 'Sports': ['Fitness', 'Outdoor', 'Team Sports', 'Water Sports', 'Winter', 'Cycling'],
271
+ 'Books': ['Fiction', 'Non-Fiction', 'Technical', 'Children', 'Comics', 'Academic'],
272
+ 'Toys': ['Action Figures', 'Board Games', 'Educational', 'Outdoor', 'Puzzles', 'Dolls'],
273
+ 'Beauty': ['Skincare', 'Makeup', 'Haircare', 'Fragrance', 'Tools', 'Mens'],
274
+ 'Automotive': ['Parts', 'Accessories', 'Tools', 'Care', 'Electronics', 'Safety'],
275
+ 'Food': ['Snacks', 'Beverages', 'Organic', 'International', 'Specialty', 'Supplements'],
276
+ 'Health': ['Vitamins', 'Supplements', 'Personal Care', 'Medical', 'Fitness', 'Wellness']
277
+ };
278
+
279
+ export const BRANDS = [
280
+ 'TechCorp', 'StyleMax', 'HomeEssentials', 'SportPro', 'BookHouse',
281
+ 'ToyWorld', 'BeautyGlow', 'AutoParts', 'FreshFoods', 'HealthPlus',
282
+ 'GadgetZone', 'FashionHub', 'ComfortLiving', 'ActiveLife', 'PageTurner'
283
+ ];