bunsane 0.3.2 → 0.5.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.
Files changed (220) hide show
  1. package/CHANGELOG.md +471 -370
  2. package/core/BatchLoader.ts +56 -32
  3. package/core/Entity.ts +93 -1020
  4. package/core/EntityHookManager.ts +52 -754
  5. package/core/Logger.ts +10 -0
  6. package/core/RequestContext.ts +94 -85
  7. package/core/RequestLoaders.ts +98 -5
  8. package/core/SchedulerManager.ts +28 -600
  9. package/core/app/cors.ts +2 -11
  10. package/core/app/preparedStatementWarmup.ts +9 -49
  11. package/core/app/requestRouter.ts +9 -8
  12. package/core/app/restRegistry.ts +8 -0
  13. package/core/archetype/fieldResolvers.ts +85 -40
  14. package/core/archetype/relationLoader.ts +135 -92
  15. package/core/cache/CacheManager.ts +91 -302
  16. package/core/cache/CompressionUtils.ts +34 -3
  17. package/core/cache/MemoryCache.ts +40 -37
  18. package/core/cache/RedisCache.ts +8 -7
  19. package/core/cache/health.ts +30 -0
  20. package/core/cache/invalidation.ts +96 -0
  21. package/core/cache/strategies/writeInvalidate.ts +111 -0
  22. package/core/cache/strategies/writeThrough.ts +233 -0
  23. package/core/components/BaseComponent.ts +25 -10
  24. package/core/components/ComponentRegistry.ts +28 -0
  25. package/core/decorators/IndexedField.ts +1 -1
  26. package/core/entity/cacheStrategies.ts +97 -0
  27. package/core/entity/componentAccess.ts +383 -0
  28. package/core/entity/finders.ts +202 -0
  29. package/core/entity/getCacheManager.ts +10 -0
  30. package/core/entity/pendingOps.ts +72 -0
  31. package/core/entity/saveEntity.ts +375 -0
  32. package/core/health.ts +93 -4
  33. package/core/hooks/dispatcher.ts +439 -0
  34. package/core/hooks/guards.ts +155 -0
  35. package/core/hooks/registry.ts +247 -0
  36. package/core/metadata/definitions/Component.ts +1 -1
  37. package/core/metadata/index.ts +15 -4
  38. package/core/middleware/RateLimit.ts +102 -105
  39. package/core/middleware/RequestId.ts +2 -9
  40. package/core/middleware/SecurityHeaders.ts +2 -11
  41. package/core/middleware/headers.ts +28 -0
  42. package/core/remote/OutboxWorker.ts +213 -183
  43. package/core/remote/RemoteManager.ts +401 -400
  44. package/core/remote/StreamConsumer.ts +535 -535
  45. package/core/remote/types.ts +153 -151
  46. package/core/requestScope.ts +34 -0
  47. package/core/scheduler/cronEvaluator.ts +174 -0
  48. package/core/scheduler/lifecycleHooks.ts +21 -0
  49. package/core/scheduler/lockCoordinator.ts +27 -0
  50. package/core/scheduler/metrics.ts +14 -0
  51. package/core/scheduler/taskRunner.ts +420 -0
  52. package/core/validateEnv.ts +10 -0
  53. package/database/DatabaseHelper.ts +128 -101
  54. package/database/IndexingStrategy.ts +72 -2
  55. package/database/PreparedStatementCache.ts +8 -2
  56. package/database/cancellable.ts +35 -22
  57. package/database/index.ts +29 -3
  58. package/database/instrumentedDb.ts +141 -141
  59. package/database/sqlHelpers.ts +3 -1
  60. package/endpoints/archetypes.ts +2 -8
  61. package/endpoints/tables.ts +6 -1
  62. package/gql/index.ts +1 -1
  63. package/gql/schema/index.ts +15 -4
  64. package/gql/visitors/ResolverGeneratorVisitor.ts +25 -4
  65. package/package.json +22 -1
  66. package/query/CTENode.ts +5 -3
  67. package/query/ComponentInclusionNode.ts +245 -14
  68. package/query/OrNode.ts +8 -19
  69. package/query/Query.ts +208 -79
  70. package/query/QueryContext.ts +6 -0
  71. package/query/QueryDAG.ts +7 -2
  72. package/query/membershipSource.ts +66 -0
  73. package/storage/LocalStorageProvider.ts +8 -3
  74. package/studio/dist/assets/index-BMZ67Npg.js +254 -0
  75. package/studio/dist/assets/index-BpbuYz9g.css +1 -0
  76. package/studio/{index.html → dist/index.html} +3 -2
  77. package/swagger/generator.ts +11 -1
  78. package/upload/UploadManager.ts +8 -6
  79. package/utils/uuid.ts +40 -10
  80. package/.claude/scheduled_tasks.lock +0 -1
  81. package/.claude/settings.local.json +0 -47
  82. package/.prettierrc +0 -4
  83. package/.serena/memories/architectural-decision-no-dependency-injection.md +0 -76
  84. package/.serena/memories/architecture.md +0 -154
  85. package/.serena/memories/cache-interface-refactoring-2026-01-24.md +0 -165
  86. package/.serena/memories/code_style_and_conventions.md +0 -76
  87. package/.serena/memories/project_overview.md +0 -43
  88. package/.serena/memories/schema-dsl-plan.md +0 -107
  89. package/.serena/memories/suggested_commands.md +0 -80
  90. package/.serena/memories/typescript-compilation-status.md +0 -54
  91. package/.serena/project.yml +0 -114
  92. package/BunSane.jpg +0 -0
  93. package/CLAUDE.md +0 -198
  94. package/TODO.md +0 -2
  95. package/bun.lock +0 -302
  96. package/bunfig.toml +0 -10
  97. package/docs/RFC_APP_REFACTOR.md +0 -248
  98. package/docs/RFC_REFACTOR_TARGETS.md +0 -251
  99. package/docs/SCALABILITY_PLAN.md +0 -175
  100. package/studio/bun.lock +0 -482
  101. package/studio/package.json +0 -39
  102. package/studio/postcss.config.js +0 -6
  103. package/studio/src/components/DataTable.tsx +0 -211
  104. package/studio/src/components/Layout.tsx +0 -13
  105. package/studio/src/components/PageContainer.tsx +0 -9
  106. package/studio/src/components/PageHeader.tsx +0 -13
  107. package/studio/src/components/SearchBar.tsx +0 -57
  108. package/studio/src/components/Sidebar.tsx +0 -294
  109. package/studio/src/components/ui/button.tsx +0 -56
  110. package/studio/src/components/ui/checkbox.tsx +0 -26
  111. package/studio/src/components/ui/input.tsx +0 -25
  112. package/studio/src/hooks/useDataTable.ts +0 -131
  113. package/studio/src/index.css +0 -36
  114. package/studio/src/lib/api.ts +0 -186
  115. package/studio/src/lib/utils.ts +0 -13
  116. package/studio/src/main.tsx +0 -17
  117. package/studio/src/pages/ArcheType.tsx +0 -239
  118. package/studio/src/pages/Components.tsx +0 -124
  119. package/studio/src/pages/EntityInspector.tsx +0 -302
  120. package/studio/src/pages/QueryRunner.tsx +0 -246
  121. package/studio/src/pages/Table.tsx +0 -94
  122. package/studio/src/pages/Welcome.tsx +0 -241
  123. package/studio/src/routes.tsx +0 -45
  124. package/studio/src/store/archeTypeSettings.ts +0 -30
  125. package/studio/src/store/studio.ts +0 -65
  126. package/studio/src/utils/columnHelpers.tsx +0 -114
  127. package/studio/studio-instructions.md +0 -81
  128. package/studio/tailwind.config.js +0 -77
  129. package/studio/utils.ts +0 -54
  130. package/studio/vite.config.js +0 -19
  131. package/tests/benchmark/BENCHMARK_DATABASES_PLAN.md +0 -338
  132. package/tests/benchmark/bunfig.toml +0 -9
  133. package/tests/benchmark/fixtures/EcommerceComponents.ts +0 -283
  134. package/tests/benchmark/fixtures/EcommerceDataGenerators.ts +0 -301
  135. package/tests/benchmark/fixtures/RelationTracker.ts +0 -159
  136. package/tests/benchmark/fixtures/index.ts +0 -6
  137. package/tests/benchmark/index.ts +0 -22
  138. package/tests/benchmark/noop-preload.ts +0 -3
  139. package/tests/benchmark/query-lateral-benchmark.test.ts +0 -372
  140. package/tests/benchmark/runners/BenchmarkLoader.ts +0 -132
  141. package/tests/benchmark/runners/index.ts +0 -4
  142. package/tests/benchmark/scenarios/query-benchmarks.test.ts +0 -465
  143. package/tests/benchmark/scripts/generate-db.ts +0 -344
  144. package/tests/benchmark/scripts/run-benchmarks.ts +0 -97
  145. package/tests/e2e/http.test.ts +0 -130
  146. package/tests/fixtures/archetypes/TestUserArchetype.ts +0 -21
  147. package/tests/fixtures/components/TestOrder.ts +0 -23
  148. package/tests/fixtures/components/TestProduct.ts +0 -23
  149. package/tests/fixtures/components/TestUser.ts +0 -20
  150. package/tests/fixtures/components/index.ts +0 -6
  151. package/tests/graphql/SchemaGeneration.test.ts +0 -90
  152. package/tests/graphql/builders/ResolverBuilder.test.ts +0 -223
  153. package/tests/graphql/builders/TypeDefBuilder.test.ts +0 -153
  154. package/tests/helpers/MockRedisClient.ts +0 -113
  155. package/tests/helpers/MockRedisStreamServer.ts +0 -448
  156. package/tests/integration/archetype/ArcheType.persistence.test.ts +0 -241
  157. package/tests/integration/cache/CacheInvalidation.test.ts +0 -259
  158. package/tests/integration/entity/Entity.persistence.test.ts +0 -333
  159. package/tests/integration/entity/Entity.saveTimeout.test.ts +0 -110
  160. package/tests/integration/loaders/RequestLoaders.abort.test.ts +0 -82
  161. package/tests/integration/query/Query.abort.test.ts +0 -66
  162. package/tests/integration/query/Query.complexAnalysis.test.ts +0 -557
  163. package/tests/integration/query/Query.edgeCases.test.ts +0 -595
  164. package/tests/integration/query/Query.exec.test.ts +0 -576
  165. package/tests/integration/query/Query.explainAnalyze.test.ts +0 -233
  166. package/tests/integration/query/Query.jsonbArray.test.ts +0 -214
  167. package/tests/integration/remote/dlq.test.ts +0 -175
  168. package/tests/integration/remote/event-dispatch.test.ts +0 -114
  169. package/tests/integration/remote/outbox.test.ts +0 -130
  170. package/tests/integration/remote/rpc.test.ts +0 -177
  171. package/tests/pglite-setup.ts +0 -62
  172. package/tests/setup.ts +0 -164
  173. package/tests/stress/BenchmarkRunner.ts +0 -203
  174. package/tests/stress/DataSeeder.ts +0 -190
  175. package/tests/stress/StressTestReporter.ts +0 -229
  176. package/tests/stress/cursor-perf-test.ts +0 -171
  177. package/tests/stress/fixtures/RealisticComponents.ts +0 -235
  178. package/tests/stress/fixtures/StressTestComponents.ts +0 -58
  179. package/tests/stress/index.ts +0 -7
  180. package/tests/stress/scenarios/query-benchmarks.test.ts +0 -285
  181. package/tests/stress/scenarios/realistic-scenarios.test.ts +0 -1081
  182. package/tests/stress/scenarios/timeout-investigation.test.ts +0 -522
  183. package/tests/unit/BatchLoader.test.ts +0 -196
  184. package/tests/unit/archetype/ArcheType.test.ts +0 -107
  185. package/tests/unit/cache/CacheManager.test.ts +0 -498
  186. package/tests/unit/cache/MemoryCache.test.ts +0 -260
  187. package/tests/unit/cache/RedisCache.test.ts +0 -411
  188. package/tests/unit/database/cancellable.test.ts +0 -81
  189. package/tests/unit/database/instrumentedDb.test.ts +0 -160
  190. package/tests/unit/entity/Entity.components.test.ts +0 -317
  191. package/tests/unit/entity/Entity.drainSideEffects.test.ts +0 -51
  192. package/tests/unit/entity/Entity.reload.test.ts +0 -63
  193. package/tests/unit/entity/Entity.requireComponents.test.ts +0 -72
  194. package/tests/unit/entity/Entity.test.ts +0 -345
  195. package/tests/unit/gql/depthLimit.test.ts +0 -203
  196. package/tests/unit/gql/operationMiddleware.test.ts +0 -293
  197. package/tests/unit/health/Health.test.ts +0 -129
  198. package/tests/unit/middleware/AccessLog.test.ts +0 -37
  199. package/tests/unit/middleware/Middleware.test.ts +0 -98
  200. package/tests/unit/middleware/RequestId.test.ts +0 -54
  201. package/tests/unit/middleware/SecurityHeaders.test.ts +0 -66
  202. package/tests/unit/query/FilterBuilder.test.ts +0 -111
  203. package/tests/unit/query/JsonbArrayBuilder.test.ts +0 -178
  204. package/tests/unit/query/Query.emptyString.test.ts +0 -69
  205. package/tests/unit/query/Query.test.ts +0 -310
  206. package/tests/unit/remote/CircuitBreaker.test.ts +0 -159
  207. package/tests/unit/remote/RemoteError.test.ts +0 -55
  208. package/tests/unit/remote/decorators.test.ts +0 -195
  209. package/tests/unit/remote/metrics.test.ts +0 -115
  210. package/tests/unit/remote/mockRedisStreamServer.test.ts +0 -104
  211. package/tests/unit/scheduler/DistributedLock.test.ts +0 -274
  212. package/tests/unit/scheduler/SchedulerManager.timeBased.test.ts +0 -95
  213. package/tests/unit/schema/schema-integration.test.ts +0 -426
  214. package/tests/unit/schema/schema.test.ts +0 -580
  215. package/tests/unit/storage/S3StorageProvider.test.ts +0 -567
  216. package/tests/unit/upload/RestUpload.test.ts +0 -267
  217. package/tests/unit/validateEnv.test.ts +0 -82
  218. package/tests/utils/entity-tracker.ts +0 -57
  219. package/tests/utils/index.ts +0 -13
  220. 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
- }