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
@@ -1,11 +1,22 @@
1
- import { generateTypeId, type BaseComponent } from "./Components";
2
- import ApplicationLifecycle, { ApplicationPhase } from "./ApplicationLifecycle";
3
- import { CreateComponentPartitionTable, GenerateTableName, UpdateComponentIndexes } from "database/DatabaseHelper";
4
- import { GetSchema } from "database/DatabaseHelper";
5
- import { logger as MainLogger } from "./Logger";
6
- import { getMetadataStorage } from "./metadata";
7
- import { registerDecoratedHooks } from "./decorators/EntityHooks";
8
- import ServiceRegistry from "service/ServiceRegistry";
1
+ import { generateTypeId } from "./Decorators";
2
+ import { type BaseComponent } from "./BaseComponent";
3
+ import ApplicationLifecycle, {
4
+ ApplicationPhase,
5
+ } from "../ApplicationLifecycle";
6
+ import {
7
+ CreateComponentPartitionTable,
8
+ GenerateTableName,
9
+ UpdateComponentIndexes,
10
+ AnalyzeAllComponentTables,
11
+ GetPartitionStrategy,
12
+ } from "../../database/DatabaseHelper";
13
+ import { ensureMultipleJSONBPathIndexes } from "../../database/IndexingStrategy";
14
+ import { GetSchema } from "../../database/DatabaseHelper";
15
+ import { logger as MainLogger } from "../Logger";
16
+ import { getMetadataStorage } from "../metadata";
17
+ import { registerDecoratedHooks } from "../decorators/EntityHooks";
18
+ import ServiceRegistry from "../../service/ServiceRegistry";
19
+ import { preparedStatementCache } from "../../database/PreparedStatementCache";
9
20
  const logger = MainLogger.child({ scope: "ComponentRegistry" });
10
21
 
11
22
  type ComponentConstructor = new () => BaseComponent;
@@ -17,15 +28,14 @@ class ComponentRegistry {
17
28
  private componentQueue = new Map<string, ComponentConstructor>();
18
29
  private currentTables: string[] = [];
19
30
  private componentsMap = new Map<string, string>();
31
+ private typeIdToName = new Map<string, string>();
20
32
  private typeIdToCtor = new Map<string, ComponentConstructor>();
21
33
  private instantRegister: boolean = false;
22
34
  private readinessPromises = new Map<string, Promise<void>>();
23
35
  private readinessResolvers = new Map<string, () => void>();
24
36
  private componentsRegistered: boolean = false;
25
37
 
26
- constructor() {
27
-
28
- }
38
+ constructor() {}
29
39
 
30
40
  public init() {
31
41
  // Listener removed to make component registration sequential
@@ -47,27 +57,27 @@ class ComponentRegistry {
47
57
  }
48
58
  }
49
59
 
50
- define(
51
- name: string,
52
- ctor: ComponentConstructor
53
- ) {
54
- if(!this.instantRegister) {
55
- if(!this.componentQueue.has(name)) {
60
+ define(name: string, ctor: ComponentConstructor) {
61
+ if (!this.instantRegister) {
62
+ if (!this.componentQueue.has(name)) {
56
63
  this.componentQueue.set(name, ctor);
57
- this.readinessPromises.set(name, new Promise<void>(resolve => {
58
- this.readinessResolvers.set(name, resolve);
59
- }));
64
+ this.readinessPromises.set(
65
+ name,
66
+ new Promise<void>((resolve) => {
67
+ this.readinessResolvers.set(name, resolve);
68
+ })
69
+ );
60
70
  return;
61
71
  }
62
72
  }
63
- if(this.instantRegister) {
64
- if(this.componentsMap.has(name)) {
73
+ if (this.instantRegister) {
74
+ if (this.componentsMap.has(name)) {
65
75
  logger.trace(`Component already registered: ${name}`);
66
76
  return;
67
77
  }
68
78
  this.register(name, generateTypeId(name), ctor).then(() => {
69
79
  const resolve = this.readinessResolvers.get(name);
70
- if(resolve) resolve();
80
+ if (resolve) resolve();
71
81
  });
72
82
  }
73
83
  }
@@ -84,16 +94,16 @@ class ComponentRegistry {
84
94
  if (this.isComponentReady(name)) {
85
95
  return Promise.resolve();
86
96
  }
87
-
97
+
88
98
  // Ensure components are registered before trying to find the component
89
99
  await this.ensureComponentsRegistered();
90
-
100
+
91
101
  if (this.isComponentReady(name)) {
92
102
  return Promise.resolve();
93
103
  }
94
-
104
+
95
105
  const storage = getMetadataStorage();
96
- const component = storage.components.find(c => c.name === name);
106
+ const component = storage.components.find((c) => c.name === name);
97
107
  if (component) {
98
108
  // Component exists in metadata but not registered yet, register it
99
109
  return this.registerComponentFromMetadata(component);
@@ -114,6 +124,27 @@ class ComponentRegistry {
114
124
  return this.componentsMap.get(name);
115
125
  }
116
126
 
127
+ getComponentName(typeId: string): string | undefined {
128
+ return this.typeIdToName.get(typeId);
129
+ }
130
+
131
+ /**
132
+ * Get component constructor by component name
133
+ * @param name Component class name
134
+ * @returns Component constructor or undefined
135
+ */
136
+ getConstructorByName(name: string): ComponentConstructor | undefined {
137
+ const typeId = this.componentsMap.get(name);
138
+ if (!typeId) return undefined;
139
+ return this.typeIdToCtor.get(typeId);
140
+ }
141
+
142
+ getPartitionTableName(typeId: string): string | null {
143
+ const name = this.typeIdToName.get(typeId);
144
+ if (!name) return null;
145
+ return GenerateTableName(name);
146
+ }
147
+
117
148
  getConstructor(typeId: string) {
118
149
  return this.typeIdToCtor.get(typeId);
119
150
  }
@@ -137,48 +168,54 @@ class ComponentRegistry {
137
168
  if (this.componentsRegistered) {
138
169
  return; // Already registered
139
170
  }
140
-
171
+
141
172
  logger.trace("Registering Components...");
142
173
  ApplicationLifecycle.setPhase(ApplicationPhase.COMPONENTS_REGISTERING);
143
-
174
+
144
175
  await this.populateCurrentTables();
145
176
  const storage = getMetadataStorage();
146
- const promises = storage.components.map(async metadata => {
177
+ const promises = storage.components.map(async (metadata) => {
147
178
  const { name, target: ctor, typeId } = metadata;
148
- if(this.componentsMap.has(name)) {
179
+ if (this.componentsMap.has(name)) {
149
180
  logger.trace(`Component already registered: ${name}`);
150
181
  return;
151
182
  }
152
- this.readinessPromises.set(name, new Promise<void>(resolve => {
153
- this.readinessResolvers.set(name, resolve);
154
- }));
183
+ this.readinessPromises.set(
184
+ name,
185
+ new Promise<void>((resolve) => {
186
+ this.readinessResolvers.set(name, resolve);
187
+ })
188
+ );
155
189
  await this.register(name, typeId, ctor as ComponentConstructor);
156
190
  const resolve = this.readinessResolvers.get(name);
157
- if(resolve) resolve();
191
+ if (resolve) resolve();
158
192
  });
159
193
  await Promise.all(promises);
160
194
  this.componentsRegistered = true;
161
-
195
+
162
196
  // Handle component-related setup that was previously in App.init()
163
197
  await this.setupComponentFeatures();
164
-
198
+
165
199
  ApplicationLifecycle.setPhase(ApplicationPhase.COMPONENTS_READY);
166
200
  }
167
201
 
168
202
  register(name: string, typeid: string, ctor: ComponentConstructor) {
169
- return new Promise<boolean>(async resolve => {
203
+ return new Promise<boolean>(async (resolve) => {
170
204
  const partitionTableName = GenerateTableName(name);
171
205
  // await this.populateCurrentTables();
172
206
  // const instance = new ctor();
173
207
  // const indexedProps = instance.indexedProperties();
174
208
  if (!this.currentTables.includes(partitionTableName)) {
175
- logger.trace(`Partition table ${partitionTableName} does not exist. Creating... name: ${name}, typeId: ${typeid}`);
209
+ logger.trace(
210
+ `Partition table ${partitionTableName} does not exist. Creating... name: ${name}, typeId: ${typeid}`
211
+ );
176
212
  // await CreateComponentPartitionTable(name, typeid, indexedProps); // TODO: OLD Logic with indexedProps, remove if not needed
177
213
  await CreateComponentPartitionTable(name, typeid);
178
214
  // await this.populateCurrentTables();
179
215
  }
180
216
  // await UpdateComponentIndexes(partitionTableName, indexedProps); // TODO: OLD Logic with indexedProps, remove if not needed
181
217
  this.componentsMap.set(name, typeid);
218
+ this.typeIdToName.set(typeid, name);
182
219
  this.typeIdToCtor.set(typeid, ctor);
183
220
  resolve(true);
184
221
  });
@@ -189,9 +226,12 @@ class ComponentRegistry {
189
226
  if (this.componentsMap.has(name)) {
190
227
  return; // Already registered
191
228
  }
192
- this.readinessPromises.set(name, new Promise<void>(resolve => {
193
- this.readinessResolvers.set(name, resolve);
194
- }));
229
+ this.readinessPromises.set(
230
+ name,
231
+ new Promise<void>((resolve) => {
232
+ this.readinessResolvers.set(name, resolve);
233
+ })
234
+ );
195
235
  await this.register(name, typeId, ctor as ComponentConstructor);
196
236
  const resolve = this.readinessResolvers.get(name);
197
237
  if (resolve) resolve();
@@ -200,21 +240,23 @@ class ComponentRegistry {
200
240
  private async registerComponentDynamically(name: string): Promise<void> {
201
241
  // Try to find the component in global metadata storage
202
242
  const storage = getMetadataStorage();
203
- const component = storage.components.find(c => c.name === name);
243
+ const component = storage.components.find((c) => c.name === name);
204
244
  if (component) {
205
245
  return this.registerComponentFromMetadata(component);
206
246
  }
207
247
 
208
248
  // If still not found, this is an error - component was never decorated
209
- throw new Error(`Component ${name} not found in metadata storage. Make sure it's decorated with @Component`);
249
+ throw new Error(
250
+ `Component ${name} not found in metadata storage. Make sure it's decorated with @Component`
251
+ );
210
252
  }
211
253
 
212
254
  getComponents() {
213
255
  // returns array of { name, ctor }
214
- const components: { name: string, ctor: ComponentConstructor }[] = [];
256
+ const components: { name: string; ctor: ComponentConstructor }[] = [];
215
257
  for (const [name, typeid] of this.componentsMap) {
216
258
  const ctor = this.typeIdToCtor.get(typeid);
217
- if(ctor) {
259
+ if (ctor) {
218
260
  components.push({ name, ctor });
219
261
  }
220
262
  }
@@ -230,16 +272,63 @@ class ComponentRegistry {
230
272
  }
231
273
  }
232
274
 
275
+ private getIndexedFieldsForComponent(componentName: string) {
276
+ const storage = getMetadataStorage();
277
+ const componentId = storage.getComponentId(componentName);
278
+ return storage.getIndexedFields(componentId);
279
+ }
280
+
233
281
  private async setupComponentFeatures(): Promise<void> {
234
282
  const components = this.getComponents();
235
-
283
+
284
+ // Invalidate prepared statement cache when component schemas change
285
+ preparedStatementCache.clear();
286
+ logger.trace(
287
+ "Cleared prepared statement cache due to component schema changes"
288
+ );
289
+
290
+ // Check partitioning strategy for index creation
291
+ const partitionStrategy = await GetPartitionStrategy();
292
+
236
293
  // Update component indexes for components that have indexed properties
237
- for(const {name, ctor} of components) {
294
+ // NOTE: Index operations are serialized to prevent deadlocks with ANALYZE
295
+ for (const { name, ctor } of components) {
238
296
  const instance = new ctor();
239
- if(instance.indexedProperties().length > 0) {
240
- const table_name = GenerateTableName(name);
241
- UpdateComponentIndexes(table_name, instance.indexedProperties());
242
- logger.trace(`Updated indexes for component: ${name}`);
297
+ const table_name = GenerateTableName(name);
298
+
299
+ // Handle legacy @CompData(indexed: true) properties
300
+ if (instance.indexedProperties().length > 0) {
301
+ // For HASH partitioning, redirect index operations to parent table
302
+ const indexTableName =
303
+ partitionStrategy === "hash" ? "components" : table_name;
304
+ await UpdateComponentIndexes(
305
+ indexTableName,
306
+ instance.indexedProperties()
307
+ );
308
+ logger.trace(
309
+ `Updated legacy indexes for component: ${name} on table: ${indexTableName}`
310
+ );
311
+ }
312
+
313
+ // Handle new @IndexedField decorators
314
+ const indexedFields = this.getIndexedFieldsForComponent(name);
315
+ if (indexedFields.length > 0) {
316
+ // For HASH partitioning, create indexes on parent table
317
+ const indexTableName =
318
+ partitionStrategy === "hash" ? "components" : table_name;
319
+ const indexDefinitions = indexedFields.map((field) => ({
320
+ tableName: indexTableName,
321
+ field: field.propertyKey,
322
+ indexType: field.indexType,
323
+ isDateField: field.isDateField,
324
+ }));
325
+ await ensureMultipleJSONBPathIndexes(
326
+ indexTableName,
327
+ indexDefinitions
328
+ );
329
+ logger.trace(
330
+ `Created specialized indexes for component: ${name} on table: ${indexTableName}`
331
+ );
243
332
  }
244
333
  }
245
334
 
@@ -249,12 +338,17 @@ class ComponentRegistry {
249
338
  try {
250
339
  registerDecoratedHooks(service);
251
340
  } catch (error) {
252
- logger.warn(`Failed to register hooks for service ${service.constructor.name}`);
341
+ logger.warn(
342
+ `Failed to register hooks for service ${service.constructor.name}`
343
+ );
253
344
  logger.warn(error);
254
345
  }
255
346
  }
256
347
  logger.info(`Registered hooks for ${services.length} services`);
348
+
349
+ // Run ANALYZE on all component tables to update query planner statistics
350
+ await AnalyzeAllComponentTables();
257
351
  }
258
352
  }
259
353
 
260
- export default ComponentRegistry.instance;
354
+ export default ComponentRegistry.instance;
@@ -0,0 +1,88 @@
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
+ import BaseComponent from './BaseComponent';
10
+
11
+ export function generateTypeId(name: string): string {
12
+ return createHash('sha256').update(name).digest('hex');
13
+ }
14
+
15
+ const primitiveTypes = [String, Number, Boolean, Symbol, BigInt, Date];
16
+
17
+ export function CompData(options?: { indexed?: boolean; nullable?: boolean; arrayOf?: any }) {
18
+ return (target: any, propertyKey: string) => {
19
+ const storage = getMetadataStorage();
20
+ const typeId = storage.getComponentId(target.constructor.name);
21
+ const propType = Reflect.getMetadata("design:type", target, propertyKey);
22
+ let isEnum = !!(Reflect.getMetadata("isEnum", propType));
23
+ // console.log(`Property ${propertyKey} type:`, propType?.name);
24
+ // console.log(`Is Enum:`, isEnum);
25
+ let enumValues: string[] | undefined = undefined;
26
+ let enumKeys: string[] | undefined = undefined;
27
+ if(isEnum) {
28
+ const metaEnumValues = Reflect.getMetadata("__enumValues", propType);
29
+ const metaEnumKeys = Reflect.getMetadata("__enumKeys", propType);
30
+
31
+ if (metaEnumValues && metaEnumKeys) {
32
+ enumValues = metaEnumValues;
33
+ enumKeys = metaEnumKeys;
34
+ } else {
35
+ const staticKeys = Object.getOwnPropertyNames(propType).filter(key =>
36
+ key !== 'prototype' &&
37
+ key !== 'length' &&
38
+ key !== 'name' &&
39
+ key !== 'isEnum' &&
40
+ key !== '__enumValues' &&
41
+ key !== '__enumKeys' &&
42
+ typeof propType[key] !== 'function' &&
43
+ typeof propType[key] !== 'boolean'
44
+ );
45
+ if (staticKeys.length > 0) {
46
+ enumValues = staticKeys.map(key => propType[key]);
47
+ enumKeys = staticKeys;
48
+ } else {
49
+ // Fallback for numeric enums
50
+ enumValues = Object.keys(propType).filter(key => !isNaN(Number(key))).map(key => propType[key]);
51
+ }
52
+ }
53
+ }
54
+ storage.collectComponentPropertyMetadata({
55
+ component_id: typeId,
56
+ propertyKey: propertyKey,
57
+ propertyType: propType,
58
+ indexed: options?.indexed ?? false,
59
+ isPrimitive: primitiveTypes.includes(propType),
60
+ isEnum: isEnum,
61
+ enumValues: enumValues,
62
+ enumKeys: enumKeys,
63
+ isOptional: options?.nullable ?? false,
64
+ arrayOf: options?.arrayOf,
65
+ })
66
+ // Reflect.metadata("compData", { isData: true, indexed: options?.indexed ?? false })(target, propertyKey);
67
+ };
68
+ }
69
+
70
+ export function Component<T extends new () => BaseComponent>(target: T): T {
71
+ const storage = getMetadataStorage();
72
+ const typeId = storage.getComponentId(target.name);
73
+ const properties = storage.getComponentProperties(typeId);
74
+ // console.log(`Component decorator applied to ${target.name} with typeId ${typeId} and properties:`, properties);
75
+ storage.collectComponentMetadata({
76
+ name: target.name,
77
+ typeId: typeId,
78
+ target: target,
79
+ });
80
+ // ComponentRegistry.define(target.name, target);
81
+ return target;
82
+ }
83
+
84
+
85
+
86
+ export type ComponentGetter<T extends BaseComponent> = Pick<T, "properties" | "id"> & {
87
+ data(): ComponentDataType<T>;
88
+ };
@@ -0,0 +1,7 @@
1
+ import type BaseComponent from './BaseComponent';
2
+ export type ComponentDataType<T extends BaseComponent> = {
3
+ [K in keyof T as T[K] extends Function ? never :
4
+ K extends `_${string}` ? never :
5
+ K extends 'id' | 'getTypeID' | 'properties' | 'data' | 'save' | 'insert' | 'update' ? never :
6
+ K]: T[K];
7
+ };
@@ -0,0 +1,5 @@
1
+ import ComponentRegistry from "./ComponentRegistry";
2
+ export { CompData, Component, type ComponentGetter} from "./Decorators";
3
+ export { BaseComponent } from "./BaseComponent";
4
+ export {type ComponentDataType} from "./Interfaces";
5
+ export {ComponentRegistry};
@@ -183,8 +183,5 @@ export function registerDecoratedHooks(serviceInstance: any): void {
183
183
  * @param serviceInstance The service instance to unregister hooks for
184
184
  */
185
185
  export function unregisterDecoratedHooks(serviceInstance: any): void {
186
- // Note: This is a simplified implementation
187
- // In a production system, you'd want to track hook IDs during registration
188
- // and use them for targeted removal here
189
186
  console.warn('unregisterDecoratedHooks is not fully implemented. Use EntityHookManager.removeHook() for individual hook removal.');
190
187
  }
@@ -0,0 +1,26 @@
1
+ import { getMetadataStorage } from '../metadata';
2
+
3
+ /**
4
+ * Decorator to mark component fields that should have dedicated database indexes
5
+ * This is used for frequently filtered fields to improve query performance
6
+ *
7
+ * @param indexType The type of index to create:
8
+ * - 'gin': GIN index for JSONB containment queries (default)
9
+ * - 'btree': BTREE index for equality and text comparisons
10
+ * - 'hash': HASH index for exact equality lookups
11
+ * - 'numeric': BTREE index with numeric cast for range queries (>, <, BETWEEN)
12
+ * @param isDateField Whether this field contains date values (affects BTREE index casting)
13
+ */
14
+ export function IndexedField(indexType: 'gin' | 'btree' | 'hash' | 'numeric' = 'gin', isDateField: boolean = false) {
15
+ return function(target: any, propertyKey: string) {
16
+ const storage = getMetadataStorage();
17
+ const componentId = storage.getComponentId(target.constructor.name);
18
+
19
+ storage.collectIndexedFieldMetadata({
20
+ componentId,
21
+ propertyKey,
22
+ indexType,
23
+ isDateField
24
+ });
25
+ };
26
+ }
@@ -5,53 +5,6 @@ import { logger } from "../Logger";
5
5
 
6
6
  const loggerInstance = logger.child({ scope: "ScheduledTaskDecorator" });
7
7
 
8
- /**
9
- * Decorator for registering scheduled tasks
10
- * @param options Task configuration options including interval and component target
11
- */
12
- export function ScheduledTask(
13
- options: ScheduledTaskOptions & {
14
- interval: ScheduleInterval;
15
- componentTarget?: new (...args: any[]) => any
16
- }
17
- ) {
18
- return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
19
- const originalMethod = descriptor.value;
20
-
21
- // Generate task ID if not provided
22
- const taskId = options.id || `${target.constructor.name}.${propertyKey}`;
23
-
24
- // Store task info for later registration
25
- if (!target.constructor.__scheduledTasks) {
26
- target.constructor.__scheduledTasks = [];
27
- }
28
-
29
- const taskInfo = {
30
- id: taskId,
31
- name: options.name || `${target.constructor.name}.${propertyKey}`,
32
- componentTarget: options.componentTarget, // Legacy support
33
- interval: options.interval,
34
- options: {
35
- runOnStart: options.runOnStart ?? false,
36
- timeout: options.timeout ?? 30000,
37
- enableLogging: options.enableLogging ?? true,
38
- ...options
39
- },
40
- service: null, // Will be set when service is instantiated
41
- methodName: propertyKey,
42
- nextExecution: new Date(),
43
- executionCount: 0,
44
- isRunning: false,
45
- enabled: true
46
- };
47
-
48
- target.constructor.__scheduledTasks.push(taskInfo);
49
-
50
- // Return the original descriptor to maintain method functionality
51
- return descriptor;
52
- };
53
- }
54
-
55
8
  /**
56
9
  * Function to manually register decorated tasks for a service instance
57
10
  * This is useful when services are instantiated outside the normal decorator flow
@@ -1,5 +1,5 @@
1
1
  import type { Entity } from "../Entity";
2
- import type { BaseComponent } from "../Components";
2
+ import type { BaseComponent } from "../components";
3
3
 
4
4
  /**
5
5
  * Base class for all entity lifecycle events
package/core/health.ts ADDED
@@ -0,0 +1,112 @@
1
+ import db from "../database";
2
+ import { CacheManager } from "./cache/CacheManager";
3
+
4
+ export interface CheckResult {
5
+ status: string;
6
+ latency_ms: number;
7
+ }
8
+
9
+ export interface HealthResponse {
10
+ status: "ok" | "degraded" | "unavailable";
11
+ timestamp: string;
12
+ uptime: number;
13
+ checks: {
14
+ database: CheckResult;
15
+ cache: CheckResult;
16
+ };
17
+ }
18
+
19
+ export interface HealthResult {
20
+ result: HealthResponse;
21
+ httpStatus: number;
22
+ }
23
+
24
+ export interface HealthDeps {
25
+ pingDb: () => Promise<boolean>;
26
+ pingCache: () => Promise<boolean>;
27
+ }
28
+
29
+ const defaultDeps: HealthDeps = {
30
+ pingDb: async () => {
31
+ await db`SELECT 1`;
32
+ return true;
33
+ },
34
+ pingCache: () => CacheManager.getInstance().ping(),
35
+ };
36
+
37
+ async function checkDatabase(pingDb: () => Promise<boolean>): Promise<CheckResult> {
38
+ const start = performance.now();
39
+ try {
40
+ await pingDb();
41
+ return { status: "up", latency_ms: Math.round(performance.now() - start) };
42
+ } catch {
43
+ return { status: "down", latency_ms: Math.round(performance.now() - start) };
44
+ }
45
+ }
46
+
47
+ async function checkCache(pingCache: () => Promise<boolean>): Promise<CheckResult> {
48
+ const start = performance.now();
49
+ try {
50
+ const ok = await pingCache();
51
+ return { status: ok ? "up" : "down", latency_ms: Math.round(performance.now() - start) };
52
+ } catch {
53
+ return { status: "down", latency_ms: Math.round(performance.now() - start) };
54
+ }
55
+ }
56
+
57
+ export async function deepHealthCheck(deps: HealthDeps = defaultDeps): Promise<HealthResult> {
58
+ const [database, cache] = await Promise.all([
59
+ checkDatabase(deps.pingDb),
60
+ checkCache(deps.pingCache),
61
+ ]);
62
+
63
+ const dbUp = database.status === "up";
64
+ const cacheUp = cache.status === "up";
65
+
66
+ let status: HealthResponse["status"];
67
+ let httpStatus: number;
68
+
69
+ if (dbUp && cacheUp) {
70
+ status = "ok";
71
+ httpStatus = 200;
72
+ } else if (dbUp && !cacheUp) {
73
+ status = "degraded";
74
+ httpStatus = 200;
75
+ } else {
76
+ status = "unavailable";
77
+ httpStatus = 503;
78
+ }
79
+
80
+ return {
81
+ result: {
82
+ status,
83
+ timestamp: new Date().toISOString(),
84
+ uptime: process.uptime(),
85
+ checks: { database, cache },
86
+ },
87
+ httpStatus,
88
+ };
89
+ }
90
+
91
+ export async function readinessCheck(
92
+ isReady: boolean,
93
+ isShuttingDown: boolean,
94
+ deps: HealthDeps = defaultDeps,
95
+ ): Promise<HealthResult> {
96
+ if (!isReady || isShuttingDown) {
97
+ return {
98
+ result: {
99
+ status: "unavailable",
100
+ timestamp: new Date().toISOString(),
101
+ uptime: process.uptime(),
102
+ checks: {
103
+ database: { status: "unknown", latency_ms: 0 },
104
+ cache: { status: "unknown", latency_ms: 0 },
105
+ },
106
+ },
107
+ httpStatus: 503,
108
+ };
109
+ }
110
+
111
+ return deepHealthCheck(deps);
112
+ }
@@ -2,8 +2,22 @@ export interface ArcheTypeMetadata {
2
2
  name: string;
3
3
  target: Function;
4
4
  typeId: string;
5
+ functions?: ArcheTypeFunctionMetadata[];
6
+ }
7
+
8
+ export interface ArcheTypeFunctionMetadata {
9
+ propertyKey: string;
10
+ options?: {
11
+ returnType?: string;
12
+ args?: Array<{
13
+ name: string;
14
+ type: any;
15
+ nullable?: boolean;
16
+ }>;
17
+ };
5
18
  }
6
19
 
7
20
  export interface ArcheTypeFieldOptions {
8
21
  nullable?: boolean;
22
+ filterable?: boolean;
9
23
  }