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