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.
- package/core/ArcheType.ts +67 -34
- package/core/BatchLoader.ts +215 -30
- package/core/Entity.ts +2 -2
- package/core/RequestContext.ts +15 -10
- package/core/RequestLoaders.ts +4 -2
- package/core/cache/CacheProvider.ts +1 -0
- package/core/cache/MemoryCache.ts +10 -1
- package/core/cache/RedisCache.ts +16 -2
- package/core/validateEnv.ts +8 -0
- package/database/DatabaseHelper.ts +113 -1
- package/database/index.ts +78 -45
- package/docs/SCALABILITY_PLAN.md +175 -0
- package/package.json +13 -2
- package/query/CTENode.ts +44 -24
- package/query/ComponentInclusionNode.ts +181 -91
- package/query/Query.ts +9 -9
- package/tests/benchmark/BENCHMARK_DATABASES_PLAN.md +338 -0
- package/tests/benchmark/bunfig.toml +9 -0
- package/tests/benchmark/fixtures/EcommerceComponents.ts +283 -0
- package/tests/benchmark/fixtures/EcommerceDataGenerators.ts +301 -0
- package/tests/benchmark/fixtures/RelationTracker.ts +159 -0
- package/tests/benchmark/fixtures/index.ts +6 -0
- package/tests/benchmark/index.ts +22 -0
- package/tests/benchmark/noop-preload.ts +3 -0
- package/tests/benchmark/runners/BenchmarkLoader.ts +132 -0
- package/tests/benchmark/runners/index.ts +4 -0
- package/tests/benchmark/scenarios/query-benchmarks.test.ts +465 -0
- package/tests/benchmark/scripts/generate-db.ts +344 -0
- package/tests/benchmark/scripts/run-benchmarks.ts +97 -0
- package/tests/integration/query/Query.complexAnalysis.test.ts +557 -0
- package/tests/integration/query/Query.explainAnalyze.test.ts +233 -0
- package/tests/stress/fixtures/RealisticComponents.ts +235 -0
- package/tests/stress/scenarios/realistic-scenarios.test.ts +1081 -0
- package/tests/stress/scenarios/timeout-investigation.test.ts +522 -0
- 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,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
|
+
];
|