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.
- package/.claude/settings.local.json +47 -0
- package/.claude/skills/update-memory.md +74 -0
- package/.prettierrc +4 -0
- package/.serena/memories/architectural-decision-no-dependency-injection.md +76 -0
- package/.serena/memories/architecture.md +154 -0
- package/.serena/memories/cache-interface-refactoring-2026-01-24.md +165 -0
- package/.serena/memories/code_style_and_conventions.md +76 -0
- package/.serena/memories/project_overview.md +43 -0
- package/.serena/memories/schema-dsl-plan.md +107 -0
- package/.serena/memories/suggested_commands.md +80 -0
- package/.serena/memories/typescript-compilation-status.md +54 -0
- package/.serena/project.yml +114 -0
- package/TODO.md +1 -7
- package/bun.lock +150 -4
- package/bunfig.toml +10 -0
- package/config/cache.config.ts +77 -0
- package/config/upload.config.ts +4 -5
- package/core/App.ts +870 -123
- package/core/ArcheType.ts +2268 -377
- package/core/BatchLoader.ts +181 -71
- package/core/Config.ts +153 -0
- package/core/Decorators.ts +4 -1
- package/core/Entity.ts +621 -92
- package/core/EntityHookManager.ts +1 -1
- package/core/EntityInterface.ts +3 -1
- package/core/EntityManager.ts +1 -13
- package/core/ErrorHandler.ts +8 -2
- package/core/Logger.ts +9 -0
- package/core/Middleware.ts +34 -0
- package/core/RequestContext.ts +5 -1
- package/core/RequestLoaders.ts +227 -93
- package/core/SchedulerManager.ts +193 -52
- package/core/cache/CacheAnalytics.ts +399 -0
- package/core/cache/CacheFactory.ts +145 -0
- package/core/cache/CacheManager.ts +520 -0
- package/core/cache/CacheProvider.ts +34 -0
- package/core/cache/CacheWarmer.ts +157 -0
- package/core/cache/CompressionUtils.ts +110 -0
- package/core/cache/MemoryCache.ts +251 -0
- package/core/cache/MultiLevelCache.ts +180 -0
- package/core/cache/NoOpCache.ts +53 -0
- package/core/cache/RedisCache.ts +464 -0
- package/core/cache/TTLStrategy.ts +254 -0
- package/core/cache/index.ts +6 -0
- package/core/components/BaseComponent.ts +120 -0
- package/core/{ComponentRegistry.ts → components/ComponentRegistry.ts} +148 -54
- package/core/components/Decorators.ts +88 -0
- package/core/components/Interfaces.ts +7 -0
- package/core/components/index.ts +5 -0
- package/core/decorators/EntityHooks.ts +0 -3
- package/core/decorators/IndexedField.ts +26 -0
- package/core/decorators/ScheduledTask.ts +0 -47
- package/core/events/EntityLifecycleEvents.ts +1 -1
- package/core/health.ts +112 -0
- package/core/metadata/definitions/ArcheType.ts +14 -0
- package/core/metadata/definitions/Component.ts +9 -0
- package/core/metadata/definitions/gqlObject.ts +1 -1
- package/core/metadata/index.ts +42 -1
- package/core/metadata/metadata-storage.ts +28 -2
- package/core/middleware/AccessLog.ts +59 -0
- package/core/middleware/RequestId.ts +38 -0
- package/core/middleware/SecurityHeaders.ts +62 -0
- package/core/middleware/index.ts +3 -0
- package/core/scheduler/DistributedLock.ts +266 -0
- package/core/scheduler/index.ts +15 -0
- package/core/validateEnv.ts +92 -0
- package/database/DatabaseHelper.ts +416 -40
- package/database/IndexingStrategy.ts +342 -0
- package/database/PreparedStatementCache.ts +226 -0
- package/database/index.ts +32 -7
- package/database/sqlHelpers.ts +14 -2
- package/endpoints/archetypes.ts +362 -0
- package/endpoints/components.ts +58 -0
- package/endpoints/entity.ts +80 -0
- package/endpoints/index.ts +27 -0
- package/endpoints/query.ts +93 -0
- package/endpoints/stats.ts +76 -0
- package/endpoints/tables.ts +212 -0
- package/endpoints/types.ts +155 -0
- package/gql/ArchetypeOperations.ts +32 -86
- package/gql/Generator.ts +27 -315
- package/gql/GeneratorV2.ts +37 -0
- package/gql/builders/InputTypeBuilder.ts +99 -0
- package/gql/builders/ResolverBuilder.ts +234 -0
- package/gql/builders/TypeDefBuilder.ts +105 -0
- package/gql/builders/index.ts +3 -0
- package/gql/decorators/Upload.ts +1 -1
- package/gql/depthLimit.ts +85 -0
- package/gql/graph/GraphNode.ts +224 -0
- package/gql/graph/SchemaGraph.ts +278 -0
- package/gql/helpers.ts +8 -2
- package/gql/index.ts +56 -4
- package/gql/middleware.ts +79 -0
- package/gql/orchestration/GraphQLSchemaOrchestrator.ts +241 -0
- package/gql/orchestration/index.ts +1 -0
- package/gql/scanner/ServiceScanner.ts +347 -0
- package/gql/schema/index.ts +458 -0
- package/gql/strategies/TypeGenerationStrategy.ts +329 -0
- package/gql/types.ts +1 -0
- package/gql/utils/TypeSignature.ts +220 -0
- package/gql/utils/index.ts +1 -0
- package/gql/visitors/ArchetypePreprocessorVisitor.ts +80 -0
- package/gql/visitors/DeduplicationVisitor.ts +82 -0
- package/gql/visitors/GraphVisitor.ts +78 -0
- package/gql/visitors/ResolverGeneratorVisitor.ts +122 -0
- package/gql/visitors/SchemaGeneratorVisitor.ts +851 -0
- package/gql/visitors/TypeCollectorVisitor.ts +79 -0
- package/gql/visitors/VisitorComposer.ts +96 -0
- package/gql/visitors/index.ts +7 -0
- package/package.json +59 -37
- package/plugins/index.ts +2 -2
- package/query/CTENode.ts +97 -0
- package/query/ComponentInclusionNode.ts +689 -0
- package/query/FilterBuilder.ts +127 -0
- package/query/FilterBuilderRegistry.ts +202 -0
- package/query/OrNode.ts +517 -0
- package/query/OrQuery.ts +42 -0
- package/query/Query.ts +1022 -0
- package/query/QueryContext.ts +170 -0
- package/query/QueryDAG.ts +122 -0
- package/query/QueryNode.ts +65 -0
- package/query/SourceNode.ts +53 -0
- package/query/builders/FullTextSearchBuilder.ts +236 -0
- package/query/index.ts +21 -0
- package/scheduler/index.ts +40 -8
- package/service/Service.ts +2 -1
- package/service/ServiceRegistry.ts +6 -5
- package/{core/storage → storage}/LocalStorageProvider.ts +2 -2
- package/storage/S3StorageProvider.ts +316 -0
- package/{core/storage → storage}/StorageProvider.ts +7 -3
- package/studio/bun.lock +482 -0
- package/studio/index.html +13 -0
- package/studio/package.json +39 -0
- package/studio/postcss.config.js +6 -0
- package/studio/src/components/DataTable.tsx +211 -0
- package/studio/src/components/Layout.tsx +13 -0
- package/studio/src/components/PageContainer.tsx +9 -0
- package/studio/src/components/PageHeader.tsx +13 -0
- package/studio/src/components/SearchBar.tsx +57 -0
- package/studio/src/components/Sidebar.tsx +294 -0
- package/studio/src/components/ui/button.tsx +56 -0
- package/studio/src/components/ui/checkbox.tsx +26 -0
- package/studio/src/components/ui/input.tsx +25 -0
- package/studio/src/hooks/useDataTable.ts +131 -0
- package/studio/src/index.css +36 -0
- package/studio/src/lib/api.ts +186 -0
- package/studio/src/lib/utils.ts +13 -0
- package/studio/src/main.tsx +17 -0
- package/studio/src/pages/ArcheType.tsx +239 -0
- package/studio/src/pages/Components.tsx +124 -0
- package/studio/src/pages/EntityInspector.tsx +302 -0
- package/studio/src/pages/QueryRunner.tsx +246 -0
- package/studio/src/pages/Table.tsx +94 -0
- package/studio/src/pages/Welcome.tsx +241 -0
- package/studio/src/routes.tsx +45 -0
- package/studio/src/store/archeTypeSettings.ts +30 -0
- package/studio/src/store/studio.ts +65 -0
- package/studio/src/utils/columnHelpers.tsx +114 -0
- package/studio/studio-instructions.md +81 -0
- package/studio/tailwind.config.js +77 -0
- package/studio/tsconfig.json +24 -0
- package/studio/utils.ts +54 -0
- package/studio/vite.config.js +19 -0
- package/swagger/generator.ts +1 -1
- package/tests/e2e/http.test.ts +126 -0
- package/tests/fixtures/archetypes/TestUserArchetype.ts +21 -0
- package/tests/fixtures/components/TestOrder.ts +23 -0
- package/tests/fixtures/components/TestProduct.ts +23 -0
- package/tests/fixtures/components/TestUser.ts +20 -0
- package/tests/fixtures/components/index.ts +6 -0
- package/tests/graphql/SchemaGeneration.test.ts +90 -0
- package/tests/graphql/builders/ResolverBuilder.test.ts +223 -0
- package/tests/graphql/builders/TypeDefBuilder.test.ts +153 -0
- package/tests/integration/archetype/ArcheType.persistence.test.ts +241 -0
- package/tests/integration/cache/CacheInvalidation.test.ts +259 -0
- package/tests/integration/entity/Entity.persistence.test.ts +333 -0
- package/tests/integration/query/Query.exec.test.ts +523 -0
- package/tests/pglite-setup.ts +61 -0
- package/tests/setup.ts +164 -0
- package/tests/stress/BenchmarkRunner.ts +203 -0
- package/tests/stress/DataSeeder.ts +190 -0
- package/tests/stress/StressTestReporter.ts +229 -0
- package/tests/stress/cursor-perf-test.ts +171 -0
- package/tests/stress/fixtures/StressTestComponents.ts +58 -0
- package/tests/stress/index.ts +7 -0
- package/tests/stress/scenarios/query-benchmarks.test.ts +285 -0
- package/tests/unit/BatchLoader.test.ts +82 -0
- package/tests/unit/archetype/ArcheType.test.ts +107 -0
- package/tests/unit/cache/CacheManager.test.ts +347 -0
- package/tests/unit/cache/MemoryCache.test.ts +260 -0
- package/tests/unit/cache/RedisCache.test.ts +411 -0
- package/tests/unit/entity/Entity.components.test.ts +244 -0
- package/tests/unit/entity/Entity.test.ts +345 -0
- package/tests/unit/gql/depthLimit.test.ts +203 -0
- package/tests/unit/gql/operationMiddleware.test.ts +293 -0
- package/tests/unit/health/Health.test.ts +129 -0
- package/tests/unit/middleware/AccessLog.test.ts +37 -0
- package/tests/unit/middleware/Middleware.test.ts +98 -0
- package/tests/unit/middleware/RequestId.test.ts +54 -0
- package/tests/unit/middleware/SecurityHeaders.test.ts +66 -0
- package/tests/unit/query/FilterBuilder.test.ts +111 -0
- package/tests/unit/query/Query.test.ts +308 -0
- package/tests/unit/scheduler/DistributedLock.test.ts +274 -0
- package/tests/unit/schema/schema-integration.test.ts +426 -0
- package/tests/unit/schema/schema.test.ts +580 -0
- package/tests/unit/storage/S3StorageProvider.test.ts +571 -0
- package/tests/unit/upload/RestUpload.test.ts +267 -0
- package/tests/unit/validateEnv.test.ts +82 -0
- package/tests/utils/entity-tracker.ts +57 -0
- package/tests/utils/index.ts +13 -0
- package/tests/utils/test-context.ts +149 -0
- package/tsconfig.json +5 -1
- package/types/archetype.types.ts +6 -0
- package/types/hooks.types.ts +1 -1
- package/types/query.types.ts +110 -0
- package/types/scheduler.types.ts +68 -7
- package/types/upload.types.ts +1 -0
- package/{core → upload}/FileValidator.ts +10 -1
- package/upload/RestUpload.ts +130 -0
- package/{core/components → upload}/UploadComponent.ts +11 -11
- package/{core → upload}/UploadManager.ts +3 -3
- package/upload/index.ts +23 -7
- package/utils/UploadHelper.ts +27 -6
- package/utils/cronParser.ts +16 -6
- package/.github/workflows/deploy-docs.yml +0 -57
- package/core/Components.ts +0 -202
- package/core/EntityCache.ts +0 -15
- package/core/Query.ts +0 -880
- package/docs/README.md +0 -149
- package/docs/_coverpage.md +0 -36
- package/docs/_sidebar.md +0 -23
- package/docs/api/core.md +0 -568
- package/docs/api/hooks.md +0 -554
- package/docs/api/index.md +0 -222
- package/docs/api/query.md +0 -678
- package/docs/api/service.md +0 -744
- package/docs/core-concepts/archetypes.md +0 -512
- package/docs/core-concepts/components.md +0 -498
- package/docs/core-concepts/entity.md +0 -314
- package/docs/core-concepts/hooks.md +0 -683
- package/docs/core-concepts/query.md +0 -588
- package/docs/core-concepts/services.md +0 -647
- package/docs/examples/code-examples.md +0 -425
- package/docs/getting-started.md +0 -337
- package/docs/index.html +0 -97
- package/tests/bench/insert.bench.ts +0 -60
- package/tests/bench/relations.bench.ts +0 -270
- package/tests/bench/sorting.bench.ts +0 -416
- package/tests/component-hooks-simple.test.ts +0 -117
- package/tests/component-hooks.test.ts +0 -1461
- package/tests/component.test.ts +0 -339
- package/tests/errorHandling.test.ts +0 -155
- package/tests/hooks.test.ts +0 -667
- package/tests/query-sorting.test.ts +0 -101
- package/tests/query.test.ts +0 -81
- package/tests/relations.test.ts +0 -170
- package/tests/scheduler.test.ts +0 -724
|
@@ -1,11 +1,22 @@
|
|
|
1
|
-
import { generateTypeId
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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(
|
|
58
|
-
|
|
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(
|
|
153
|
-
|
|
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(
|
|
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(
|
|
193
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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(
|
|
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
|
+
};
|
|
@@ -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
|
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
|
}
|