bunsane 0.3.1 → 0.4.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/CHANGELOG.md +445 -318
- package/config/cache.config.ts +35 -1
- package/core/App.ts +24 -1064
- package/core/ArcheType.ts +78 -2110
- package/core/BatchLoader.ts +56 -32
- package/core/Entity.ts +85 -1043
- package/core/EntityHookManager.ts +52 -754
- package/core/Logger.ts +10 -0
- package/core/RequestContext.ts +64 -6
- package/core/RequestLoaders.ts +187 -36
- package/core/SchedulerManager.ts +28 -600
- package/core/app/bootstrap.ts +133 -0
- package/core/app/cors.ts +85 -0
- package/core/app/graphqlSetup.ts +56 -0
- package/core/app/healthEndpoints.ts +31 -0
- package/core/app/metricsCollector.ts +27 -0
- package/core/app/preparedStatementWarmup.ts +15 -0
- package/core/app/processHandlers.ts +43 -0
- package/core/app/requestRouter.ts +310 -0
- package/core/app/restRegistry.ts +80 -0
- package/core/app/shutdown.ts +97 -0
- package/core/app/studioRouter.ts +83 -0
- package/core/archetype/customTypes.ts +100 -0
- package/core/archetype/decorators.ts +171 -0
- package/core/archetype/fieldResolvers.ts +666 -0
- package/core/archetype/helpers.ts +29 -0
- package/core/archetype/relationLoader.ts +161 -0
- package/core/archetype/schemaBuilder.ts +141 -0
- package/core/archetype/weaver.ts +218 -0
- package/core/archetype/zodSchemaBuilder.ts +527 -0
- package/core/cache/CacheManager.ts +173 -267
- package/core/cache/CompressionUtils.ts +34 -3
- package/core/cache/MemoryCache.ts +40 -37
- package/core/cache/RedisCache.ts +4 -4
- package/core/cache/health.ts +30 -0
- package/core/cache/invalidation.ts +96 -0
- package/core/cache/strategies/writeInvalidate.ts +111 -0
- package/core/cache/strategies/writeThrough.ts +233 -0
- package/core/components/BaseComponent.ts +16 -8
- package/core/components/ComponentRegistry.ts +28 -0
- package/core/decorators/IndexedField.ts +1 -1
- package/core/entity/cacheStrategies.ts +97 -0
- package/core/entity/componentAccess.ts +364 -0
- package/core/entity/finders.ts +202 -0
- package/core/entity/pendingOps.ts +72 -0
- package/core/entity/saveEntity.ts +377 -0
- package/core/hooks/dispatcher.ts +439 -0
- package/core/hooks/guards.ts +155 -0
- package/core/hooks/registry.ts +247 -0
- package/core/metadata/definitions/Component.ts +1 -1
- package/core/metadata/index.ts +15 -4
- package/core/middleware/AccessLog.ts +8 -1
- package/core/middleware/RateLimit.ts +102 -105
- package/core/middleware/RequestId.ts +2 -9
- package/core/middleware/SecurityHeaders.ts +2 -11
- package/core/middleware/headers.ts +28 -0
- package/core/remote/OutboxWorker.ts +213 -183
- package/core/remote/RemoteManager.ts +401 -400
- package/core/remote/types.ts +153 -151
- package/core/requestScope.ts +34 -0
- package/core/scheduler/cronEvaluator.ts +174 -0
- package/core/scheduler/lifecycleHooks.ts +21 -0
- package/core/scheduler/lockCoordinator.ts +27 -0
- package/core/scheduler/metrics.ts +14 -0
- package/core/scheduler/taskRunner.ts +420 -0
- package/database/DatabaseHelper.ts +128 -101
- package/database/IndexingStrategy.ts +72 -2
- package/database/PreparedStatementCache.ts +20 -5
- package/database/cancellable.ts +35 -0
- package/database/index.ts +15 -3
- package/database/instrumentedDb.ts +141 -0
- package/endpoints/archetypes.ts +2 -8
- package/endpoints/tables.ts +6 -1
- package/gql/index.ts +1 -1
- package/gql/visitors/ResolverGeneratorVisitor.ts +25 -4
- package/package.json +22 -1
- package/query/CTENode.ts +5 -3
- package/query/ComponentInclusionNode.ts +240 -13
- package/query/OrNode.ts +6 -5
- package/query/Query.ts +203 -59
- package/query/QueryContext.ts +6 -0
- package/query/QueryDAG.ts +7 -2
- package/query/membershipSource.ts +66 -0
- package/storage/LocalStorageProvider.ts +8 -3
- package/studio/dist/assets/index-BMZ67Npg.js +254 -0
- package/studio/dist/assets/index-BpbuYz9g.css +1 -0
- package/studio/{index.html → dist/index.html} +3 -2
- package/swagger/generator.ts +11 -1
- package/upload/UploadManager.ts +8 -6
- package/utils/uuid.ts +40 -10
- package/.claude/settings.local.json +0 -47
- package/.prettierrc +0 -4
- package/.serena/memories/architectural-decision-no-dependency-injection.md +0 -76
- package/.serena/memories/architecture.md +0 -154
- package/.serena/memories/cache-interface-refactoring-2026-01-24.md +0 -165
- package/.serena/memories/code_style_and_conventions.md +0 -76
- package/.serena/memories/project_overview.md +0 -43
- package/.serena/memories/schema-dsl-plan.md +0 -107
- package/.serena/memories/suggested_commands.md +0 -80
- package/.serena/memories/typescript-compilation-status.md +0 -54
- package/.serena/project.yml +0 -114
- package/BunSane.jpg +0 -0
- package/CLAUDE.md +0 -198
- package/TODO.md +0 -2
- package/bun.lock +0 -302
- package/bunfig.toml +0 -10
- package/docs/SCALABILITY_PLAN.md +0 -175
- package/studio/bun.lock +0 -482
- package/studio/package.json +0 -39
- package/studio/postcss.config.js +0 -6
- package/studio/src/components/DataTable.tsx +0 -211
- package/studio/src/components/Layout.tsx +0 -13
- package/studio/src/components/PageContainer.tsx +0 -9
- package/studio/src/components/PageHeader.tsx +0 -13
- package/studio/src/components/SearchBar.tsx +0 -57
- package/studio/src/components/Sidebar.tsx +0 -294
- package/studio/src/components/ui/button.tsx +0 -56
- package/studio/src/components/ui/checkbox.tsx +0 -26
- package/studio/src/components/ui/input.tsx +0 -25
- package/studio/src/hooks/useDataTable.ts +0 -131
- package/studio/src/index.css +0 -36
- package/studio/src/lib/api.ts +0 -186
- package/studio/src/lib/utils.ts +0 -13
- package/studio/src/main.tsx +0 -17
- package/studio/src/pages/ArcheType.tsx +0 -239
- package/studio/src/pages/Components.tsx +0 -124
- package/studio/src/pages/EntityInspector.tsx +0 -302
- package/studio/src/pages/QueryRunner.tsx +0 -246
- package/studio/src/pages/Table.tsx +0 -94
- package/studio/src/pages/Welcome.tsx +0 -241
- package/studio/src/routes.tsx +0 -45
- package/studio/src/store/archeTypeSettings.ts +0 -30
- package/studio/src/store/studio.ts +0 -65
- package/studio/src/utils/columnHelpers.tsx +0 -114
- package/studio/studio-instructions.md +0 -81
- package/studio/tailwind.config.js +0 -77
- package/studio/utils.ts +0 -54
- package/studio/vite.config.js +0 -19
- package/tests/benchmark/BENCHMARK_DATABASES_PLAN.md +0 -338
- package/tests/benchmark/bunfig.toml +0 -9
- package/tests/benchmark/fixtures/EcommerceComponents.ts +0 -283
- package/tests/benchmark/fixtures/EcommerceDataGenerators.ts +0 -301
- package/tests/benchmark/fixtures/RelationTracker.ts +0 -159
- package/tests/benchmark/fixtures/index.ts +0 -6
- package/tests/benchmark/index.ts +0 -22
- package/tests/benchmark/noop-preload.ts +0 -3
- package/tests/benchmark/query-lateral-benchmark.test.ts +0 -372
- package/tests/benchmark/runners/BenchmarkLoader.ts +0 -132
- package/tests/benchmark/runners/index.ts +0 -4
- package/tests/benchmark/scenarios/query-benchmarks.test.ts +0 -465
- package/tests/benchmark/scripts/generate-db.ts +0 -344
- package/tests/benchmark/scripts/run-benchmarks.ts +0 -97
- package/tests/e2e/http.test.ts +0 -130
- package/tests/fixtures/archetypes/TestUserArchetype.ts +0 -21
- package/tests/fixtures/components/TestOrder.ts +0 -23
- package/tests/fixtures/components/TestProduct.ts +0 -23
- package/tests/fixtures/components/TestUser.ts +0 -20
- package/tests/fixtures/components/index.ts +0 -6
- package/tests/graphql/SchemaGeneration.test.ts +0 -90
- package/tests/graphql/builders/ResolverBuilder.test.ts +0 -223
- package/tests/graphql/builders/TypeDefBuilder.test.ts +0 -153
- package/tests/helpers/MockRedisClient.ts +0 -113
- package/tests/helpers/MockRedisStreamServer.ts +0 -448
- package/tests/integration/archetype/ArcheType.persistence.test.ts +0 -241
- package/tests/integration/cache/CacheInvalidation.test.ts +0 -259
- package/tests/integration/entity/Entity.persistence.test.ts +0 -333
- package/tests/integration/entity/Entity.saveTimeout.test.ts +0 -110
- package/tests/integration/query/Query.complexAnalysis.test.ts +0 -557
- package/tests/integration/query/Query.edgeCases.test.ts +0 -595
- package/tests/integration/query/Query.exec.test.ts +0 -576
- package/tests/integration/query/Query.explainAnalyze.test.ts +0 -233
- package/tests/integration/query/Query.jsonbArray.test.ts +0 -214
- package/tests/integration/remote/dlq.test.ts +0 -175
- package/tests/integration/remote/event-dispatch.test.ts +0 -114
- package/tests/integration/remote/outbox.test.ts +0 -130
- package/tests/integration/remote/rpc.test.ts +0 -177
- package/tests/pglite-setup.ts +0 -62
- package/tests/setup.ts +0 -164
- package/tests/stress/BenchmarkRunner.ts +0 -203
- package/tests/stress/DataSeeder.ts +0 -190
- package/tests/stress/StressTestReporter.ts +0 -229
- package/tests/stress/cursor-perf-test.ts +0 -171
- package/tests/stress/fixtures/RealisticComponents.ts +0 -235
- package/tests/stress/fixtures/StressTestComponents.ts +0 -58
- package/tests/stress/index.ts +0 -7
- package/tests/stress/scenarios/query-benchmarks.test.ts +0 -285
- package/tests/stress/scenarios/realistic-scenarios.test.ts +0 -1081
- package/tests/stress/scenarios/timeout-investigation.test.ts +0 -522
- package/tests/unit/BatchLoader.test.ts +0 -196
- package/tests/unit/archetype/ArcheType.test.ts +0 -107
- package/tests/unit/cache/CacheManager.test.ts +0 -367
- package/tests/unit/cache/MemoryCache.test.ts +0 -260
- package/tests/unit/cache/RedisCache.test.ts +0 -411
- package/tests/unit/entity/Entity.components.test.ts +0 -317
- package/tests/unit/entity/Entity.drainSideEffects.test.ts +0 -51
- package/tests/unit/entity/Entity.reload.test.ts +0 -63
- package/tests/unit/entity/Entity.requireComponents.test.ts +0 -72
- package/tests/unit/entity/Entity.test.ts +0 -345
- package/tests/unit/gql/depthLimit.test.ts +0 -203
- package/tests/unit/gql/operationMiddleware.test.ts +0 -293
- package/tests/unit/health/Health.test.ts +0 -129
- package/tests/unit/middleware/AccessLog.test.ts +0 -37
- package/tests/unit/middleware/Middleware.test.ts +0 -98
- package/tests/unit/middleware/RequestId.test.ts +0 -54
- package/tests/unit/middleware/SecurityHeaders.test.ts +0 -66
- package/tests/unit/query/FilterBuilder.test.ts +0 -111
- package/tests/unit/query/JsonbArrayBuilder.test.ts +0 -178
- package/tests/unit/query/Query.emptyString.test.ts +0 -69
- package/tests/unit/query/Query.test.ts +0 -310
- package/tests/unit/remote/CircuitBreaker.test.ts +0 -159
- package/tests/unit/remote/RemoteError.test.ts +0 -55
- package/tests/unit/remote/decorators.test.ts +0 -195
- package/tests/unit/remote/metrics.test.ts +0 -115
- package/tests/unit/remote/mockRedisStreamServer.test.ts +0 -104
- package/tests/unit/scheduler/DistributedLock.test.ts +0 -274
- package/tests/unit/scheduler/SchedulerManager.timeBased.test.ts +0 -95
- package/tests/unit/schema/schema-integration.test.ts +0 -426
- package/tests/unit/schema/schema.test.ts +0 -580
- package/tests/unit/storage/S3StorageProvider.test.ts +0 -567
- package/tests/unit/upload/RestUpload.test.ts +0 -267
- package/tests/unit/validateEnv.test.ts +0 -82
- package/tests/utils/entity-tracker.ts +0 -57
- package/tests/utils/index.ts +0 -13
- package/tests/utils/test-context.ts +0 -149
|
@@ -1,283 +0,0 @@
|
|
|
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
|
-
];
|
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Data generators for benchmark e-commerce components.
|
|
3
|
-
*
|
|
4
|
-
* Uses deterministic seeding for reproducible data generation.
|
|
5
|
-
* Implements realistic distributions (power-law, exponential).
|
|
6
|
-
*/
|
|
7
|
-
import {
|
|
8
|
-
USER_TIERS, USER_STATUSES, PRODUCT_STATUSES, ORDER_STATUSES,
|
|
9
|
-
ORDER_ITEM_STATUSES, PAYMENT_METHODS, REGIONS, CATEGORIES,
|
|
10
|
-
SUBCATEGORIES, BRANDS
|
|
11
|
-
} from './EcommerceComponents';
|
|
12
|
-
import type { RelationTracker } from './RelationTracker';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Seeded random number generator (Mulberry32)
|
|
16
|
-
*/
|
|
17
|
-
export class SeededRandom {
|
|
18
|
-
private state: number;
|
|
19
|
-
|
|
20
|
-
constructor(seed: number) {
|
|
21
|
-
this.state = seed;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
next(): number {
|
|
25
|
-
let t = this.state += 0x6D2B79F5;
|
|
26
|
-
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
27
|
-
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
28
|
-
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
nextInt(min: number, max: number): number {
|
|
32
|
-
return Math.floor(this.next() * (max - min + 1)) + min;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
pick<T>(arr: readonly T[]): T {
|
|
36
|
-
return arr[Math.floor(this.next() * arr.length)]!;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
pickWeighted<T>(arr: readonly T[], weights: number[]): T {
|
|
40
|
-
const total = weights.reduce((a, b) => a + b, 0);
|
|
41
|
-
let r = this.next() * total;
|
|
42
|
-
for (let i = 0; i < arr.length; i++) {
|
|
43
|
-
r -= weights[i]!;
|
|
44
|
-
if (r <= 0) return arr[i]!;
|
|
45
|
-
}
|
|
46
|
-
return arr[arr.length - 1]!;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Power-law distribution (Pareto-like)
|
|
51
|
-
* Used for category popularity, user activity, etc.
|
|
52
|
-
*/
|
|
53
|
-
powerLaw(min: number, max: number, alpha: number = 1.5): number {
|
|
54
|
-
const u = this.next();
|
|
55
|
-
const minP = Math.pow(min, 1 - alpha);
|
|
56
|
-
const maxP = Math.pow(max, 1 - alpha);
|
|
57
|
-
return Math.pow((maxP - minP) * u + minP, 1 / (1 - alpha));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Exponential distribution
|
|
62
|
-
* Used for order frequency, review counts, etc.
|
|
63
|
-
*/
|
|
64
|
-
exponential(lambda: number = 1): number {
|
|
65
|
-
return -Math.log(1 - this.next()) / lambda;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const FIRST_NAMES = [
|
|
70
|
-
'James', 'Mary', 'John', 'Patricia', 'Robert', 'Jennifer', 'Michael', 'Linda',
|
|
71
|
-
'William', 'Elizabeth', 'David', 'Barbara', 'Richard', 'Susan', 'Joseph', 'Jessica',
|
|
72
|
-
'Thomas', 'Sarah', 'Charles', 'Karen', 'Christopher', 'Lisa', 'Daniel', 'Nancy',
|
|
73
|
-
'Alex', 'Emma', 'Olivia', 'Liam', 'Noah', 'Ava', 'Sophia', 'Isabella'
|
|
74
|
-
];
|
|
75
|
-
|
|
76
|
-
const LAST_NAMES = [
|
|
77
|
-
'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis',
|
|
78
|
-
'Rodriguez', 'Martinez', 'Hernandez', 'Lopez', 'Gonzalez', 'Wilson', 'Anderson',
|
|
79
|
-
'Thomas', 'Taylor', 'Moore', 'Jackson', 'Martin', 'Lee', 'Perez', 'Thompson', 'White'
|
|
80
|
-
];
|
|
81
|
-
|
|
82
|
-
const ADJECTIVES = [
|
|
83
|
-
'Premium', 'Professional', 'Ultra', 'Classic', 'Essential', 'Advanced',
|
|
84
|
-
'Pro', 'Elite', 'Basic', 'Standard', 'Deluxe', 'Supreme', 'Ultimate'
|
|
85
|
-
];
|
|
86
|
-
|
|
87
|
-
const PRODUCT_NOUNS = [
|
|
88
|
-
'Widget', 'Device', 'Tool', 'Kit', 'Set', 'Pack', 'Bundle',
|
|
89
|
-
'System', 'Unit', 'Module', 'Gear', 'Equipment', 'Accessory'
|
|
90
|
-
];
|
|
91
|
-
|
|
92
|
-
const REVIEW_TITLES = [
|
|
93
|
-
'Great product!', 'Exactly what I needed', 'Highly recommended',
|
|
94
|
-
'Good value', 'Works perfectly', 'Exceeded expectations',
|
|
95
|
-
'Not bad', 'Could be better', 'Disappointed', 'Amazing quality',
|
|
96
|
-
'Fast shipping', 'Would buy again', 'Perfect fit', 'Love it'
|
|
97
|
-
];
|
|
98
|
-
|
|
99
|
-
const REVIEW_CONTENT_TEMPLATES = [
|
|
100
|
-
'This product is exactly what I was looking for. {adj} quality and {adj2} performance.',
|
|
101
|
-
'I\'ve been using this for {weeks} weeks now and it\'s been {adj}.',
|
|
102
|
-
'The {feature} is really {adj}. Would recommend to anyone looking for a {type}.',
|
|
103
|
-
'Shipping was fast and the product arrived in perfect condition. Very {adj}.',
|
|
104
|
-
'For the price, this is an {adj} deal. The {feature} works great.',
|
|
105
|
-
'Had some issues initially but customer support was {adj}. Now it works perfectly.',
|
|
106
|
-
'Compared to other {type}s I\'ve tried, this one is by far the most {adj}.',
|
|
107
|
-
'The build quality is {adj}. Feels {adj2} in hand.'
|
|
108
|
-
];
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Generate user data
|
|
112
|
-
*/
|
|
113
|
-
export function generateUserData(index: number, rng: SeededRandom) {
|
|
114
|
-
const firstName = rng.pick(FIRST_NAMES);
|
|
115
|
-
const lastName = rng.pick(LAST_NAMES);
|
|
116
|
-
const username = `${firstName.toLowerCase()}${lastName.toLowerCase()}${index}`;
|
|
117
|
-
|
|
118
|
-
// Power-law distribution for tier (most users are free)
|
|
119
|
-
const tierWeights = [0.6, 0.25, 0.12, 0.03]; // free, basic, premium, enterprise
|
|
120
|
-
const tier = rng.pickWeighted(USER_TIERS, tierWeights);
|
|
121
|
-
|
|
122
|
-
// Most users are active
|
|
123
|
-
const statusWeights = [0.85, 0.12, 0.03];
|
|
124
|
-
const status = rng.pickWeighted(USER_STATUSES, statusWeights);
|
|
125
|
-
|
|
126
|
-
const createdAt = new Date(Date.now() - rng.nextInt(0, 365 * 2) * 24 * 60 * 60 * 1000);
|
|
127
|
-
const hasLoggedIn = rng.next() > 0.1;
|
|
128
|
-
const lastLoginAt = hasLoggedIn
|
|
129
|
-
? new Date(createdAt.getTime() + rng.nextInt(0, Date.now() - createdAt.getTime()))
|
|
130
|
-
: null;
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
email: `${username}@example.com`,
|
|
134
|
-
username,
|
|
135
|
-
firstName,
|
|
136
|
-
lastName,
|
|
137
|
-
status,
|
|
138
|
-
tier,
|
|
139
|
-
region: rng.pick(REGIONS),
|
|
140
|
-
orderCount: Math.floor(rng.exponential(0.1)),
|
|
141
|
-
totalSpent: Math.floor(rng.exponential(0.01) * 100),
|
|
142
|
-
createdAt,
|
|
143
|
-
lastLoginAt
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Generate product data
|
|
149
|
-
*/
|
|
150
|
-
export function generateProductData(index: number, rng: SeededRandom) {
|
|
151
|
-
const category = rng.pick(CATEGORIES);
|
|
152
|
-
const subcategories = SUBCATEGORIES[category] || ['General'];
|
|
153
|
-
const subcategory = rng.pick(subcategories);
|
|
154
|
-
const brand = rng.pick(BRANDS);
|
|
155
|
-
|
|
156
|
-
const adjective = rng.pick(ADJECTIVES);
|
|
157
|
-
const noun = rng.pick(PRODUCT_NOUNS);
|
|
158
|
-
const name = `${brand} ${adjective} ${noun} ${subcategory}`;
|
|
159
|
-
const sku = `${category.substring(0, 3).toUpperCase()}-${index.toString().padStart(6, '0')}`;
|
|
160
|
-
|
|
161
|
-
const basePrice = Math.floor(rng.powerLaw(10, 1000, 1.2));
|
|
162
|
-
const cost = Math.floor(basePrice * (0.3 + rng.next() * 0.3));
|
|
163
|
-
|
|
164
|
-
// Power-law for stock (most products have moderate stock)
|
|
165
|
-
const stock = Math.floor(rng.powerLaw(0, 1000, 1.5));
|
|
166
|
-
|
|
167
|
-
const statusWeights = [0.85, 0.10, 0.05];
|
|
168
|
-
const status = rng.pickWeighted(PRODUCT_STATUSES, statusWeights);
|
|
169
|
-
|
|
170
|
-
return {
|
|
171
|
-
sku,
|
|
172
|
-
name,
|
|
173
|
-
description: `${name} - High quality ${subcategory.toLowerCase()} from ${brand}. Features include premium materials and excellent craftsmanship.`,
|
|
174
|
-
category,
|
|
175
|
-
subcategory,
|
|
176
|
-
brand,
|
|
177
|
-
price: basePrice,
|
|
178
|
-
cost,
|
|
179
|
-
stock,
|
|
180
|
-
status,
|
|
181
|
-
rating: Math.round((3 + rng.next() * 2) * 10) / 10, // 3.0 - 5.0
|
|
182
|
-
reviewCount: Math.floor(rng.exponential(0.05)),
|
|
183
|
-
createdAt: new Date(Date.now() - rng.nextInt(0, 365 * 3) * 24 * 60 * 60 * 1000)
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Generate order data
|
|
189
|
-
*/
|
|
190
|
-
export function generateOrderData(
|
|
191
|
-
index: number,
|
|
192
|
-
rng: SeededRandom,
|
|
193
|
-
tracker: RelationTracker
|
|
194
|
-
) {
|
|
195
|
-
const userId = tracker.getRandomUserId(rng);
|
|
196
|
-
const orderNumber = `ORD-${Date.now().toString(36).toUpperCase()}-${index.toString().padStart(6, '0')}`;
|
|
197
|
-
|
|
198
|
-
// Most orders are delivered
|
|
199
|
-
const statusWeights = [0.05, 0.10, 0.15, 0.60, 0.07, 0.03];
|
|
200
|
-
const status = rng.pickWeighted(ORDER_STATUSES, statusWeights);
|
|
201
|
-
|
|
202
|
-
const subtotal = Math.floor(rng.powerLaw(20, 500, 1.3));
|
|
203
|
-
const tax = Math.floor(subtotal * 0.08);
|
|
204
|
-
const shipping = subtotal > 50 ? 0 : Math.floor(5 + rng.next() * 10);
|
|
205
|
-
const total = subtotal + tax + shipping;
|
|
206
|
-
|
|
207
|
-
const orderedAt = new Date(Date.now() - rng.nextInt(0, 365) * 24 * 60 * 60 * 1000);
|
|
208
|
-
const isShipped = ['shipped', 'delivered'].includes(status);
|
|
209
|
-
const shippedAt = isShipped
|
|
210
|
-
? new Date(orderedAt.getTime() + rng.nextInt(1, 5) * 24 * 60 * 60 * 1000)
|
|
211
|
-
: null;
|
|
212
|
-
const deliveredAt = status === 'delivered' && shippedAt
|
|
213
|
-
? new Date(shippedAt.getTime() + rng.nextInt(1, 7) * 24 * 60 * 60 * 1000)
|
|
214
|
-
: null;
|
|
215
|
-
|
|
216
|
-
return {
|
|
217
|
-
userId,
|
|
218
|
-
orderNumber,
|
|
219
|
-
status,
|
|
220
|
-
subtotal,
|
|
221
|
-
tax,
|
|
222
|
-
shipping,
|
|
223
|
-
total,
|
|
224
|
-
itemCount: rng.nextInt(1, 5),
|
|
225
|
-
paymentMethod: rng.pick(PAYMENT_METHODS),
|
|
226
|
-
shippingRegion: rng.pick(REGIONS),
|
|
227
|
-
orderedAt,
|
|
228
|
-
shippedAt,
|
|
229
|
-
deliveredAt
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Generate order item data
|
|
235
|
-
*/
|
|
236
|
-
export function generateOrderItemData(
|
|
237
|
-
index: number,
|
|
238
|
-
rng: SeededRandom,
|
|
239
|
-
tracker: RelationTracker,
|
|
240
|
-
orderId?: string,
|
|
241
|
-
orderUserId?: string
|
|
242
|
-
) {
|
|
243
|
-
const resolvedOrderId = orderId || tracker.getRandomOrderId(rng);
|
|
244
|
-
const productId = tracker.getRandomProductId(rng);
|
|
245
|
-
const userId = orderUserId || tracker.getOrderUserId(resolvedOrderId) || tracker.getRandomUserId(rng);
|
|
246
|
-
|
|
247
|
-
const quantity = rng.nextInt(1, 3);
|
|
248
|
-
const unitPrice = Math.floor(rng.powerLaw(10, 200, 1.3));
|
|
249
|
-
const discount = rng.next() < 0.3 ? Math.floor(unitPrice * rng.next() * 0.3) : 0;
|
|
250
|
-
const total = (unitPrice - discount) * quantity;
|
|
251
|
-
|
|
252
|
-
const statusWeights = [0.10, 0.80, 0.07, 0.03];
|
|
253
|
-
const status = rng.pickWeighted(ORDER_ITEM_STATUSES, statusWeights);
|
|
254
|
-
|
|
255
|
-
return {
|
|
256
|
-
orderId: resolvedOrderId,
|
|
257
|
-
productId,
|
|
258
|
-
userId,
|
|
259
|
-
quantity,
|
|
260
|
-
unitPrice,
|
|
261
|
-
discount,
|
|
262
|
-
total,
|
|
263
|
-
status
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Generate review data
|
|
269
|
-
*/
|
|
270
|
-
export function generateReviewData(
|
|
271
|
-
index: number,
|
|
272
|
-
rng: SeededRandom,
|
|
273
|
-
tracker: RelationTracker
|
|
274
|
-
) {
|
|
275
|
-
const productId = tracker.getRandomProductId(rng);
|
|
276
|
-
const userId = tracker.getRandomUserId(rng);
|
|
277
|
-
|
|
278
|
-
// Rating distribution skewed toward positive
|
|
279
|
-
const ratingWeights = [0.05, 0.08, 0.15, 0.32, 0.40]; // 1-5 stars
|
|
280
|
-
const rating = rng.pickWeighted([1, 2, 3, 4, 5], ratingWeights);
|
|
281
|
-
|
|
282
|
-
const title = rng.pick(REVIEW_TITLES);
|
|
283
|
-
const template = rng.pick(REVIEW_CONTENT_TEMPLATES);
|
|
284
|
-
const content = template
|
|
285
|
-
.replace('{adj}', rng.pick(['excellent', 'great', 'good', 'decent', 'amazing']))
|
|
286
|
-
.replace('{adj2}', rng.pick(['solid', 'reliable', 'impressive', 'premium']))
|
|
287
|
-
.replace('{weeks}', String(rng.nextInt(1, 12)))
|
|
288
|
-
.replace('{feature}', rng.pick(['design', 'build quality', 'performance', 'value']))
|
|
289
|
-
.replace('{type}', rng.pick(['product', 'item', 'device']));
|
|
290
|
-
|
|
291
|
-
return {
|
|
292
|
-
productId,
|
|
293
|
-
userId,
|
|
294
|
-
rating,
|
|
295
|
-
title,
|
|
296
|
-
content,
|
|
297
|
-
verified: rng.next() > 0.3,
|
|
298
|
-
helpfulCount: Math.floor(rng.exponential(0.2)),
|
|
299
|
-
createdAt: new Date(Date.now() - rng.nextInt(0, 365) * 24 * 60 * 60 * 1000)
|
|
300
|
-
};
|
|
301
|
-
}
|