bunsane 0.1.4 → 0.2.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 (257) hide show
  1. package/.claude/settings.local.json +47 -0
  2. package/.claude/skills/update-memory.md +74 -0
  3. package/.prettierrc +4 -0
  4. package/.serena/memories/architectural-decision-no-dependency-injection.md +76 -0
  5. package/.serena/memories/architecture.md +154 -0
  6. package/.serena/memories/cache-interface-refactoring-2026-01-24.md +165 -0
  7. package/.serena/memories/code_style_and_conventions.md +76 -0
  8. package/.serena/memories/project_overview.md +43 -0
  9. package/.serena/memories/schema-dsl-plan.md +107 -0
  10. package/.serena/memories/suggested_commands.md +80 -0
  11. package/.serena/memories/typescript-compilation-status.md +54 -0
  12. package/.serena/project.yml +114 -0
  13. package/TODO.md +1 -7
  14. package/bun.lock +150 -4
  15. package/bunfig.toml +10 -0
  16. package/config/cache.config.ts +77 -0
  17. package/config/upload.config.ts +4 -5
  18. package/core/App.ts +870 -123
  19. package/core/ArcheType.ts +2268 -377
  20. package/core/BatchLoader.ts +181 -71
  21. package/core/Config.ts +153 -0
  22. package/core/Decorators.ts +4 -1
  23. package/core/Entity.ts +621 -92
  24. package/core/EntityHookManager.ts +1 -1
  25. package/core/EntityInterface.ts +3 -1
  26. package/core/EntityManager.ts +1 -13
  27. package/core/ErrorHandler.ts +8 -2
  28. package/core/Logger.ts +9 -0
  29. package/core/Middleware.ts +34 -0
  30. package/core/RequestContext.ts +5 -1
  31. package/core/RequestLoaders.ts +227 -93
  32. package/core/SchedulerManager.ts +193 -52
  33. package/core/cache/CacheAnalytics.ts +399 -0
  34. package/core/cache/CacheFactory.ts +145 -0
  35. package/core/cache/CacheManager.ts +520 -0
  36. package/core/cache/CacheProvider.ts +34 -0
  37. package/core/cache/CacheWarmer.ts +157 -0
  38. package/core/cache/CompressionUtils.ts +110 -0
  39. package/core/cache/MemoryCache.ts +251 -0
  40. package/core/cache/MultiLevelCache.ts +180 -0
  41. package/core/cache/NoOpCache.ts +53 -0
  42. package/core/cache/RedisCache.ts +464 -0
  43. package/core/cache/TTLStrategy.ts +254 -0
  44. package/core/cache/index.ts +6 -0
  45. package/core/components/BaseComponent.ts +120 -0
  46. package/core/{ComponentRegistry.ts → components/ComponentRegistry.ts} +148 -54
  47. package/core/components/Decorators.ts +88 -0
  48. package/core/components/Interfaces.ts +7 -0
  49. package/core/components/index.ts +5 -0
  50. package/core/decorators/EntityHooks.ts +0 -3
  51. package/core/decorators/IndexedField.ts +26 -0
  52. package/core/decorators/ScheduledTask.ts +0 -47
  53. package/core/events/EntityLifecycleEvents.ts +1 -1
  54. package/core/health.ts +112 -0
  55. package/core/metadata/definitions/ArcheType.ts +14 -0
  56. package/core/metadata/definitions/Component.ts +9 -0
  57. package/core/metadata/definitions/gqlObject.ts +1 -1
  58. package/core/metadata/index.ts +42 -1
  59. package/core/metadata/metadata-storage.ts +28 -2
  60. package/core/middleware/AccessLog.ts +59 -0
  61. package/core/middleware/RequestId.ts +38 -0
  62. package/core/middleware/SecurityHeaders.ts +62 -0
  63. package/core/middleware/index.ts +3 -0
  64. package/core/scheduler/DistributedLock.ts +266 -0
  65. package/core/scheduler/index.ts +15 -0
  66. package/core/validateEnv.ts +92 -0
  67. package/database/DatabaseHelper.ts +416 -40
  68. package/database/IndexingStrategy.ts +342 -0
  69. package/database/PreparedStatementCache.ts +226 -0
  70. package/database/index.ts +32 -7
  71. package/database/sqlHelpers.ts +14 -2
  72. package/endpoints/archetypes.ts +362 -0
  73. package/endpoints/components.ts +58 -0
  74. package/endpoints/entity.ts +80 -0
  75. package/endpoints/index.ts +27 -0
  76. package/endpoints/query.ts +93 -0
  77. package/endpoints/stats.ts +76 -0
  78. package/endpoints/tables.ts +212 -0
  79. package/endpoints/types.ts +155 -0
  80. package/gql/ArchetypeOperations.ts +32 -86
  81. package/gql/Generator.ts +27 -315
  82. package/gql/GeneratorV2.ts +37 -0
  83. package/gql/builders/InputTypeBuilder.ts +99 -0
  84. package/gql/builders/ResolverBuilder.ts +234 -0
  85. package/gql/builders/TypeDefBuilder.ts +105 -0
  86. package/gql/builders/index.ts +3 -0
  87. package/gql/decorators/Upload.ts +1 -1
  88. package/gql/depthLimit.ts +85 -0
  89. package/gql/graph/GraphNode.ts +224 -0
  90. package/gql/graph/SchemaGraph.ts +278 -0
  91. package/gql/helpers.ts +8 -2
  92. package/gql/index.ts +56 -4
  93. package/gql/middleware.ts +79 -0
  94. package/gql/orchestration/GraphQLSchemaOrchestrator.ts +241 -0
  95. package/gql/orchestration/index.ts +1 -0
  96. package/gql/scanner/ServiceScanner.ts +347 -0
  97. package/gql/schema/index.ts +458 -0
  98. package/gql/strategies/TypeGenerationStrategy.ts +329 -0
  99. package/gql/types.ts +1 -0
  100. package/gql/utils/TypeSignature.ts +220 -0
  101. package/gql/utils/index.ts +1 -0
  102. package/gql/visitors/ArchetypePreprocessorVisitor.ts +80 -0
  103. package/gql/visitors/DeduplicationVisitor.ts +82 -0
  104. package/gql/visitors/GraphVisitor.ts +78 -0
  105. package/gql/visitors/ResolverGeneratorVisitor.ts +122 -0
  106. package/gql/visitors/SchemaGeneratorVisitor.ts +851 -0
  107. package/gql/visitors/TypeCollectorVisitor.ts +79 -0
  108. package/gql/visitors/VisitorComposer.ts +96 -0
  109. package/gql/visitors/index.ts +7 -0
  110. package/package.json +59 -37
  111. package/plugins/index.ts +2 -2
  112. package/query/CTENode.ts +97 -0
  113. package/query/ComponentInclusionNode.ts +689 -0
  114. package/query/FilterBuilder.ts +127 -0
  115. package/query/FilterBuilderRegistry.ts +202 -0
  116. package/query/OrNode.ts +517 -0
  117. package/query/OrQuery.ts +42 -0
  118. package/query/Query.ts +1022 -0
  119. package/query/QueryContext.ts +170 -0
  120. package/query/QueryDAG.ts +122 -0
  121. package/query/QueryNode.ts +65 -0
  122. package/query/SourceNode.ts +53 -0
  123. package/query/builders/FullTextSearchBuilder.ts +236 -0
  124. package/query/index.ts +21 -0
  125. package/scheduler/index.ts +40 -8
  126. package/service/Service.ts +2 -1
  127. package/service/ServiceRegistry.ts +6 -5
  128. package/{core/storage → storage}/LocalStorageProvider.ts +2 -2
  129. package/storage/S3StorageProvider.ts +316 -0
  130. package/{core/storage → storage}/StorageProvider.ts +7 -3
  131. package/studio/bun.lock +482 -0
  132. package/studio/index.html +13 -0
  133. package/studio/package.json +39 -0
  134. package/studio/postcss.config.js +6 -0
  135. package/studio/src/components/DataTable.tsx +211 -0
  136. package/studio/src/components/Layout.tsx +13 -0
  137. package/studio/src/components/PageContainer.tsx +9 -0
  138. package/studio/src/components/PageHeader.tsx +13 -0
  139. package/studio/src/components/SearchBar.tsx +57 -0
  140. package/studio/src/components/Sidebar.tsx +294 -0
  141. package/studio/src/components/ui/button.tsx +56 -0
  142. package/studio/src/components/ui/checkbox.tsx +26 -0
  143. package/studio/src/components/ui/input.tsx +25 -0
  144. package/studio/src/hooks/useDataTable.ts +131 -0
  145. package/studio/src/index.css +36 -0
  146. package/studio/src/lib/api.ts +186 -0
  147. package/studio/src/lib/utils.ts +13 -0
  148. package/studio/src/main.tsx +17 -0
  149. package/studio/src/pages/ArcheType.tsx +239 -0
  150. package/studio/src/pages/Components.tsx +124 -0
  151. package/studio/src/pages/EntityInspector.tsx +302 -0
  152. package/studio/src/pages/QueryRunner.tsx +246 -0
  153. package/studio/src/pages/Table.tsx +94 -0
  154. package/studio/src/pages/Welcome.tsx +241 -0
  155. package/studio/src/routes.tsx +45 -0
  156. package/studio/src/store/archeTypeSettings.ts +30 -0
  157. package/studio/src/store/studio.ts +65 -0
  158. package/studio/src/utils/columnHelpers.tsx +114 -0
  159. package/studio/studio-instructions.md +81 -0
  160. package/studio/tailwind.config.js +77 -0
  161. package/studio/tsconfig.json +24 -0
  162. package/studio/utils.ts +54 -0
  163. package/studio/vite.config.js +19 -0
  164. package/swagger/generator.ts +1 -1
  165. package/tests/e2e/http.test.ts +126 -0
  166. package/tests/fixtures/archetypes/TestUserArchetype.ts +21 -0
  167. package/tests/fixtures/components/TestOrder.ts +23 -0
  168. package/tests/fixtures/components/TestProduct.ts +23 -0
  169. package/tests/fixtures/components/TestUser.ts +20 -0
  170. package/tests/fixtures/components/index.ts +6 -0
  171. package/tests/graphql/SchemaGeneration.test.ts +90 -0
  172. package/tests/graphql/builders/ResolverBuilder.test.ts +223 -0
  173. package/tests/graphql/builders/TypeDefBuilder.test.ts +153 -0
  174. package/tests/integration/archetype/ArcheType.persistence.test.ts +241 -0
  175. package/tests/integration/cache/CacheInvalidation.test.ts +259 -0
  176. package/tests/integration/entity/Entity.persistence.test.ts +333 -0
  177. package/tests/integration/query/Query.exec.test.ts +523 -0
  178. package/tests/pglite-setup.ts +61 -0
  179. package/tests/setup.ts +164 -0
  180. package/tests/stress/BenchmarkRunner.ts +203 -0
  181. package/tests/stress/DataSeeder.ts +190 -0
  182. package/tests/stress/StressTestReporter.ts +229 -0
  183. package/tests/stress/cursor-perf-test.ts +171 -0
  184. package/tests/stress/fixtures/StressTestComponents.ts +58 -0
  185. package/tests/stress/index.ts +7 -0
  186. package/tests/stress/scenarios/query-benchmarks.test.ts +285 -0
  187. package/tests/unit/BatchLoader.test.ts +82 -0
  188. package/tests/unit/archetype/ArcheType.test.ts +107 -0
  189. package/tests/unit/cache/CacheManager.test.ts +347 -0
  190. package/tests/unit/cache/MemoryCache.test.ts +260 -0
  191. package/tests/unit/cache/RedisCache.test.ts +411 -0
  192. package/tests/unit/entity/Entity.components.test.ts +244 -0
  193. package/tests/unit/entity/Entity.test.ts +345 -0
  194. package/tests/unit/gql/depthLimit.test.ts +203 -0
  195. package/tests/unit/gql/operationMiddleware.test.ts +293 -0
  196. package/tests/unit/health/Health.test.ts +129 -0
  197. package/tests/unit/middleware/AccessLog.test.ts +37 -0
  198. package/tests/unit/middleware/Middleware.test.ts +98 -0
  199. package/tests/unit/middleware/RequestId.test.ts +54 -0
  200. package/tests/unit/middleware/SecurityHeaders.test.ts +66 -0
  201. package/tests/unit/query/FilterBuilder.test.ts +111 -0
  202. package/tests/unit/query/Query.test.ts +308 -0
  203. package/tests/unit/scheduler/DistributedLock.test.ts +274 -0
  204. package/tests/unit/schema/schema-integration.test.ts +426 -0
  205. package/tests/unit/schema/schema.test.ts +580 -0
  206. package/tests/unit/storage/S3StorageProvider.test.ts +571 -0
  207. package/tests/unit/upload/RestUpload.test.ts +267 -0
  208. package/tests/unit/validateEnv.test.ts +82 -0
  209. package/tests/utils/entity-tracker.ts +57 -0
  210. package/tests/utils/index.ts +13 -0
  211. package/tests/utils/test-context.ts +149 -0
  212. package/tsconfig.json +5 -1
  213. package/types/archetype.types.ts +6 -0
  214. package/types/hooks.types.ts +1 -1
  215. package/types/query.types.ts +110 -0
  216. package/types/scheduler.types.ts +68 -7
  217. package/types/upload.types.ts +1 -0
  218. package/{core → upload}/FileValidator.ts +10 -1
  219. package/upload/RestUpload.ts +130 -0
  220. package/{core/components → upload}/UploadComponent.ts +11 -11
  221. package/{core → upload}/UploadManager.ts +3 -3
  222. package/upload/index.ts +23 -7
  223. package/utils/UploadHelper.ts +27 -6
  224. package/utils/cronParser.ts +16 -6
  225. package/.github/workflows/deploy-docs.yml +0 -57
  226. package/core/Components.ts +0 -202
  227. package/core/EntityCache.ts +0 -15
  228. package/core/Query.ts +0 -880
  229. package/docs/README.md +0 -149
  230. package/docs/_coverpage.md +0 -36
  231. package/docs/_sidebar.md +0 -23
  232. package/docs/api/core.md +0 -568
  233. package/docs/api/hooks.md +0 -554
  234. package/docs/api/index.md +0 -222
  235. package/docs/api/query.md +0 -678
  236. package/docs/api/service.md +0 -744
  237. package/docs/core-concepts/archetypes.md +0 -512
  238. package/docs/core-concepts/components.md +0 -498
  239. package/docs/core-concepts/entity.md +0 -314
  240. package/docs/core-concepts/hooks.md +0 -683
  241. package/docs/core-concepts/query.md +0 -588
  242. package/docs/core-concepts/services.md +0 -647
  243. package/docs/examples/code-examples.md +0 -425
  244. package/docs/getting-started.md +0 -337
  245. package/docs/index.html +0 -97
  246. package/tests/bench/insert.bench.ts +0 -60
  247. package/tests/bench/relations.bench.ts +0 -270
  248. package/tests/bench/sorting.bench.ts +0 -416
  249. package/tests/component-hooks-simple.test.ts +0 -117
  250. package/tests/component-hooks.test.ts +0 -1461
  251. package/tests/component.test.ts +0 -339
  252. package/tests/errorHandling.test.ts +0 -155
  253. package/tests/hooks.test.ts +0 -667
  254. package/tests/query-sorting.test.ts +0 -101
  255. package/tests/query.test.ts +0 -81
  256. package/tests/relations.test.ts +0 -170
  257. package/tests/scheduler.test.ts +0 -724
@@ -0,0 +1,254 @@
1
+ import type { CacheProvider, CacheStats } from './CacheProvider.js';
2
+
3
+ /**
4
+ * TTLStrategy implements dynamic TTL management based on data access patterns.
5
+ * Tracks access frequency and adjusts TTL accordingly to optimize cache efficiency.
6
+ *
7
+ * Features:
8
+ * - Hot data detection: doubles TTL for frequently accessed data
9
+ * - Cold data detection: halves TTL for rarely accessed data
10
+ * - Access pattern tracking with sliding window
11
+ * - Configurable thresholds for hot/cold classification
12
+ */
13
+ export class TTLStrategy {
14
+ private accessCounts: Map<string, number> = new Map();
15
+ private lastAccessTime: Map<string, number> = new Map();
16
+ private baseTTL: number;
17
+ private hotThreshold: number;
18
+ private coldThreshold: number;
19
+ private windowSize: number;
20
+
21
+ constructor(
22
+ baseTTL: number = 3600000, // 1 hour default
23
+ hotThreshold: number = 10, // 10 accesses = hot
24
+ coldThreshold: number = 1, // 1 access = cold
25
+ windowSize: number = 300000 // 5 minutes window
26
+ ) {
27
+ this.baseTTL = baseTTL;
28
+ this.hotThreshold = hotThreshold;
29
+ this.coldThreshold = coldThreshold;
30
+ this.windowSize = windowSize;
31
+ }
32
+
33
+ /**
34
+ * Records an access to a cache key and returns the appropriate TTL
35
+ */
36
+ recordAccess(key: string): number {
37
+ const now = Date.now();
38
+ const lastAccess = this.lastAccessTime.get(key) || 0;
39
+ const timeDiff = now - lastAccess;
40
+
41
+ // Reset count if outside window
42
+ if (timeDiff > this.windowSize) {
43
+ this.accessCounts.set(key, 1);
44
+ } else {
45
+ const currentCount = this.accessCounts.get(key) || 0;
46
+ this.accessCounts.set(key, currentCount + 1);
47
+ }
48
+
49
+ this.lastAccessTime.set(key, now);
50
+
51
+ const accessCount = this.accessCounts.get(key) || 0;
52
+ return this.calculateTTL(accessCount);
53
+ }
54
+
55
+ /**
56
+ * Calculates TTL based on access frequency
57
+ */
58
+ private calculateTTL(accessCount: number): number {
59
+ if (accessCount >= this.hotThreshold) {
60
+ // Hot data: double TTL
61
+ return this.baseTTL * 2;
62
+ } else if (accessCount <= this.coldThreshold) {
63
+ // Cold data: halve TTL
64
+ return Math.max(this.baseTTL / 2, 60000); // Minimum 1 minute
65
+ } else {
66
+ // Normal data: base TTL
67
+ return this.baseTTL;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Gets current access statistics for a key
73
+ */
74
+ getAccessStats(key: string): { count: number; lastAccess: number; ttl: number; category: 'hot' | 'cold' | 'normal' } {
75
+ const count = this.accessCounts.get(key) || 0;
76
+ const lastAccess = this.lastAccessTime.get(key) || 0;
77
+ const ttl = this.calculateTTL(count);
78
+
79
+ let category: 'hot' | 'cold' | 'normal';
80
+ if (count >= this.hotThreshold) {
81
+ category = 'hot';
82
+ } else if (count <= this.coldThreshold) {
83
+ category = 'cold';
84
+ } else {
85
+ category = 'normal';
86
+ }
87
+
88
+ return { count, lastAccess, ttl, category };
89
+ }
90
+
91
+ /**
92
+ * Cleans up old access records to prevent memory leaks
93
+ */
94
+ cleanup(maxAge: number = 3600000): void { // 1 hour default
95
+ const now = Date.now();
96
+ const keysToDelete: string[] = [];
97
+
98
+ for (const [key, lastAccess] of this.lastAccessTime) {
99
+ if (now - lastAccess > maxAge) {
100
+ keysToDelete.push(key);
101
+ }
102
+ }
103
+
104
+ for (const key of keysToDelete) {
105
+ this.accessCounts.delete(key);
106
+ this.lastAccessTime.delete(key);
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Gets overall statistics
112
+ */
113
+ getStats(): {
114
+ totalKeys: number;
115
+ hotKeys: number;
116
+ coldKeys: number;
117
+ normalKeys: number;
118
+ averageAccessCount: number;
119
+ } {
120
+ let hotKeys = 0;
121
+ let coldKeys = 0;
122
+ let normalKeys = 0;
123
+ let totalAccessCount = 0;
124
+
125
+ for (const [key, count] of this.accessCounts) {
126
+ totalAccessCount += count;
127
+
128
+ if (count >= this.hotThreshold) {
129
+ hotKeys++;
130
+ } else if (count <= this.coldThreshold) {
131
+ coldKeys++;
132
+ } else {
133
+ normalKeys++;
134
+ }
135
+ }
136
+
137
+ const totalKeys = this.accessCounts.size;
138
+ const averageAccessCount = totalKeys > 0 ? totalAccessCount / totalKeys : 0;
139
+
140
+ return {
141
+ totalKeys,
142
+ hotKeys,
143
+ coldKeys,
144
+ normalKeys,
145
+ averageAccessCount
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Resets all tracking data
151
+ */
152
+ reset(): void {
153
+ this.accessCounts.clear();
154
+ this.lastAccessTime.clear();
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Enhanced cache provider that integrates TTL strategy
160
+ */
161
+ export class AdaptiveTTLProvider implements CacheProvider {
162
+ private cache: CacheProvider;
163
+ private ttlStrategy: TTLStrategy;
164
+
165
+ constructor(cache: CacheProvider, ttlStrategy: TTLStrategy) {
166
+ this.cache = cache;
167
+ this.ttlStrategy = ttlStrategy;
168
+ }
169
+
170
+ async get(key: string): Promise<any | null> {
171
+ const result = await this.cache.get(key);
172
+ if (result !== null) {
173
+ // Record access for TTL adjustment
174
+ this.ttlStrategy.recordAccess(key);
175
+ }
176
+ return result;
177
+ }
178
+
179
+ async set(key: string, value: any, ttl?: number): Promise<void> {
180
+ // Use TTL strategy if no explicit TTL provided
181
+ const effectiveTTL = ttl || this.ttlStrategy.recordAccess(key);
182
+ await this.cache.set(key, value, effectiveTTL);
183
+ }
184
+
185
+ async delete(key: string | string[]): Promise<void> {
186
+ await this.cache.delete(key);
187
+ }
188
+
189
+ async clear(): Promise<void> {
190
+ await this.cache.clear();
191
+ this.ttlStrategy.reset();
192
+ }
193
+
194
+ async getMany<T>(keys: string[]): Promise<(T | null)[]> {
195
+ const results = await this.cache.getMany<T>(keys);
196
+
197
+ // Record access for found keys
198
+ for (let i = 0; i < keys.length; i++) {
199
+ const key = keys[i];
200
+ if (results[i] !== null && key !== undefined) {
201
+ this.ttlStrategy.recordAccess(key);
202
+ }
203
+ }
204
+
205
+ return results;
206
+ }
207
+
208
+ async setMany<T>(entries: Array<{key: string, value: T, ttl?: number}>): Promise<void> {
209
+ // Apply adaptive TTL to entries without explicit TTL
210
+ const entriesWithTTL = entries.map(entry => ({
211
+ ...entry,
212
+ ttl: entry.ttl || this.ttlStrategy.recordAccess(entry.key)
213
+ }));
214
+
215
+ await this.cache.setMany(entriesWithTTL);
216
+ }
217
+
218
+ async deleteMany(keys: string[]): Promise<void> {
219
+ await this.cache.deleteMany(keys);
220
+ }
221
+
222
+ async invalidatePattern(pattern: string): Promise<void> {
223
+ await this.cache.invalidatePattern(pattern);
224
+ }
225
+
226
+ async ping(): Promise<boolean> {
227
+ return await this.cache.ping();
228
+ }
229
+
230
+ async getStats(): Promise<CacheStats> {
231
+ return await this.cache.getStats();
232
+ }
233
+
234
+ /**
235
+ * Gets TTL strategy statistics
236
+ */
237
+ getTTLStats() {
238
+ return this.ttlStrategy.getStats();
239
+ }
240
+
241
+ /**
242
+ * Gets access stats for a specific key
243
+ */
244
+ getKeyAccessStats(key: string) {
245
+ return this.ttlStrategy.getAccessStats(key);
246
+ }
247
+
248
+ /**
249
+ * Cleans up old access records
250
+ */
251
+ cleanupAccessRecords(maxAge?: number) {
252
+ this.ttlStrategy.cleanup(maxAge);
253
+ }
254
+ }
@@ -0,0 +1,6 @@
1
+ export type { CacheProvider, CacheStats } from './CacheProvider';
2
+ export { MemoryCache } from './MemoryCache';
3
+ export type { MemoryCacheConfig } from './MemoryCache';
4
+ export { NoOpCache } from './NoOpCache';
5
+ export { CacheManager } from './CacheManager';
6
+ export { CacheFactory } from './CacheFactory';
@@ -0,0 +1,120 @@
1
+ import { createHash } from 'crypto';
2
+ import "reflect-metadata";
3
+ import { logger as MainLogger } from "../Logger";
4
+ import ComponentRegistry from "./ComponentRegistry";
5
+ import { type ComponentDataType } from './Interfaces';
6
+ import { uuidv7 } from '../../utils/uuid';
7
+ import { getMetadataStorage } from '../metadata';
8
+ const logger = MainLogger.child({ scope: "Components" });
9
+
10
+ export class BaseComponent {
11
+ public id: string = "";
12
+ protected _comp_name: string = "";
13
+ protected _typeId: string = "";
14
+ protected _persisted: boolean = false;
15
+ protected _dirty: boolean = false;
16
+
17
+ constructor() {
18
+ this._comp_name = this.constructor.name;
19
+ const storage = getMetadataStorage();
20
+ this._typeId = storage.getComponentId(this._comp_name);
21
+ this._dirty = false;
22
+ }
23
+
24
+ getTypeID(): string {
25
+ return this._typeId;
26
+ }
27
+
28
+ properties(): string[] {
29
+ const storage = getMetadataStorage();
30
+ const props = storage.componentProperties.get(this._typeId);
31
+ if(!props) return [];
32
+ return props.map(p => p.propertyKey);
33
+ }
34
+
35
+ /**
36
+ * Get data for this component
37
+ * @returns Object containing only properties marked with @CompData decorator
38
+ */
39
+ data<T extends this>(): ComponentDataType<T> {
40
+ const data: Record<string, any> = {};
41
+ this.properties().forEach((prop: string) => {
42
+ data[prop] = (this as any)[prop];
43
+ });
44
+ return data as ComponentDataType<T>;
45
+ }
46
+
47
+ /**
48
+ * Get serializable data for database storage
49
+ * @returns Object with Dates serialized to ISO strings
50
+ */
51
+ serializableData(): Record<string, any> {
52
+ const data: Record<string, any> = {};
53
+ const storage = getMetadataStorage();
54
+ const props = storage.componentProperties.get(this._typeId);
55
+ this.properties().forEach((prop: string) => {
56
+ let value = (this as any)[prop];
57
+ const propMeta = props?.find(p => p.propertyKey === prop);
58
+ if (propMeta?.propertyType === Date && value instanceof Date) {
59
+ value = value.toISOString();
60
+ }
61
+ data[prop] = value;
62
+ });
63
+ return data;
64
+ }
65
+
66
+ async save(trx: Bun.SQL, entity_id: string) {
67
+ logger.trace(`Saving component ${this._comp_name} for entity ${entity_id}`);
68
+ // Only check readiness if component is not yet registered
69
+ // This optimization avoids 40,000+ unnecessary async calls for bulk operations
70
+ if(!ComponentRegistry.isComponentReady(this._comp_name)) {
71
+ logger.trace(`Checking is Component can be saved (is registered)`);
72
+ await ComponentRegistry.getReadyPromise(this._comp_name);
73
+ logger.trace(`Component Registered`);
74
+ }
75
+ if(this._persisted) {
76
+ await this.update(trx);
77
+ } else {
78
+ await this.insert(trx, entity_id);
79
+ this._persisted = true;
80
+ }
81
+ }
82
+
83
+ async insert(trx: Bun.SQL, entity_id: string) {
84
+ if(this.id === "") {
85
+ this.id = uuidv7();
86
+ }
87
+ // Validate entity_id to prevent PostgreSQL UUID parsing errors
88
+ if (!entity_id || entity_id.trim() === '') {
89
+ throw new Error(`Cannot insert component ${this._comp_name}: entity_id is empty or invalid`);
90
+ }
91
+ await trx`INSERT INTO components
92
+ (id, entity_id, name, type_id, data)
93
+ VALUES (${this.id}, ${entity_id}, ${this._comp_name}, ${this._typeId}, ${this.serializableData()})`
94
+ await trx`INSERT INTO entity_components (entity_id, type_id, component_id) VALUES (${entity_id}, ${this._typeId}, ${this.id}) ON CONFLICT DO NOTHING`
95
+ }
96
+
97
+ async update(trx: Bun.SQL) {
98
+ if(this.id === "") {
99
+ throw new Error("Component must have an ID to be updated");
100
+ }
101
+ await trx`UPDATE components SET data = ${this.serializableData()} WHERE id = ${this.id}`
102
+ }
103
+
104
+ public setPersisted(persisted: boolean) {
105
+ this._persisted = persisted;
106
+ }
107
+
108
+ public setDirty(dirty: boolean) {
109
+ this._dirty = dirty;
110
+ }
111
+
112
+ indexedProperties(): string[] {
113
+ const storage = getMetadataStorage();
114
+ const props = storage.componentProperties.get(this._typeId);
115
+ if(!props) return [];
116
+ return props.filter(p => p.indexed).map(p => p.propertyKey);
117
+ }
118
+ }
119
+
120
+ export default BaseComponent;