@acorex/platform 20.0.5 → 20.0.7
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/auth/index.d.ts +37 -32
- package/common/index.d.ts +104 -4
- package/core/index.d.ts +4 -0
- package/domain/index.d.ts +2010 -42
- package/fesm2022/acorex-platform-auth.mjs +57 -21
- package/fesm2022/acorex-platform-auth.mjs.map +1 -1
- package/fesm2022/acorex-platform-common.mjs +332 -90
- package/fesm2022/acorex-platform-common.mjs.map +1 -1
- package/fesm2022/acorex-platform-core.mjs +54 -42
- package/fesm2022/acorex-platform-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-domain.mjs +3338 -31
- package/fesm2022/acorex-platform-domain.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-builder.mjs +174 -168
- package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-components.mjs +281 -89
- package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-designer.mjs +72 -72
- package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-entity.mjs +210 -130
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-views.mjs +342 -119
- package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
- package/fesm2022/acorex-platform-native.mjs +7 -7
- package/fesm2022/acorex-platform-native.mjs.map +1 -1
- package/fesm2022/acorex-platform-runtime.mjs +51 -48
- package/fesm2022/acorex-platform-runtime.mjs.map +1 -1
- package/fesm2022/{acorex-platform-themes-default-create-entity-view.component-BxYe1IlE.mjs → acorex-platform-themes-default-create-entity-view.component-DyVB3PZT.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-default-create-entity-view.component-BxYe1IlE.mjs.map → acorex-platform-themes-default-create-entity-view.component-DyVB3PZT.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-themes-default-entity-master-create-view.component-CzSqhYfE.mjs → acorex-platform-themes-default-entity-master-create-view.component-CsWVOu-r.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-default-entity-master-create-view.component-CzSqhYfE.mjs.map → acorex-platform-themes-default-entity-master-create-view.component-CsWVOu-r.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-themes-default-entity-master-list-view.component-BxdQ5JWS.mjs → acorex-platform-themes-default-entity-master-list-view.component-DgyzWR29.mjs} +20 -20
- package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-DgyzWR29.mjs.map +1 -0
- package/fesm2022/{acorex-platform-themes-default-entity-master-modify-view.component-12xcM8FJ.mjs → acorex-platform-themes-default-entity-master-modify-view.component-gJap_TBH.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-default-entity-master-modify-view.component-12xcM8FJ.mjs.map → acorex-platform-themes-default-entity-master-modify-view.component-gJap_TBH.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-themes-default-entity-master-single-view.component-D99jLndX.mjs → acorex-platform-themes-default-entity-master-single-view.component-BUNm9JV5.mjs} +8 -8
- package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-BUNm9JV5.mjs.map +1 -0
- package/fesm2022/{acorex-platform-themes-default-error-401.component-8QfQ3hNo.mjs → acorex-platform-themes-default-error-401.component-DP3msj3d.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-default-error-401.component-8QfQ3hNo.mjs.map → acorex-platform-themes-default-error-401.component-DP3msj3d.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-themes-default-error-404.component-D9npA9_W.mjs → acorex-platform-themes-default-error-404.component-uvBHGbHY.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-default-error-404.component-D9npA9_W.mjs.map → acorex-platform-themes-default-error-404.component-uvBHGbHY.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-themes-default-error-offline.component-BQpVnLzt.mjs → acorex-platform-themes-default-error-offline.component-CdXIik4U.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-default-error-offline.component-BQpVnLzt.mjs.map → acorex-platform-themes-default-error-offline.component-CdXIik4U.mjs.map} +1 -1
- package/fesm2022/acorex-platform-themes-default.mjs +469 -51
- package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
- package/fesm2022/acorex-platform-themes-shared.mjs +22 -23
- package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
- package/fesm2022/{acorex-platform-widgets-button-widget-designer.component-BIZkWv8q.mjs → acorex-platform-widgets-button-widget-designer.component-CgUkYMwV.mjs} +4 -4
- package/fesm2022/{acorex-platform-widgets-button-widget-designer.component-BIZkWv8q.mjs.map → acorex-platform-widgets-button-widget-designer.component-CgUkYMwV.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-widgets-checkbox-widget-column.component-MjSzOXIJ.mjs → acorex-platform-widgets-checkbox-widget-column.component-Dn0U56O7.mjs} +4 -4
- package/fesm2022/{acorex-platform-widgets-checkbox-widget-column.component-MjSzOXIJ.mjs.map → acorex-platform-widgets-checkbox-widget-column.component-Dn0U56O7.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-widgets-checkbox-widget-designer.component-5kuroU6r.mjs → acorex-platform-widgets-checkbox-widget-designer.component-BD0-kCi0.mjs} +5 -5
- package/fesm2022/acorex-platform-widgets-checkbox-widget-designer.component-BD0-kCi0.mjs.map +1 -0
- package/fesm2022/{acorex-platform-widgets-checkbox-widget-view.component-BgrIeNW0.mjs → acorex-platform-widgets-checkbox-widget-view.component-CzCWGDil.mjs} +4 -4
- package/fesm2022/{acorex-platform-widgets-checkbox-widget-view.component-BgrIeNW0.mjs.map → acorex-platform-widgets-checkbox-widget-view.component-CzCWGDil.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-widgets-color-box-widget-designer.component-BTxAgvMQ.mjs → acorex-platform-widgets-color-box-widget-designer.component-BSRAHIPQ.mjs} +4 -4
- package/fesm2022/{acorex-platform-widgets-color-box-widget-designer.component-BTxAgvMQ.mjs.map → acorex-platform-widgets-color-box-widget-designer.component-BSRAHIPQ.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-widgets-contact-widget-filter.component-DCUfKMJ6.mjs → acorex-platform-widgets-contact-widget-filter.component-CtCAgeH9.mjs} +4 -4
- package/fesm2022/{acorex-platform-widgets-contact-widget-filter.component-DCUfKMJ6.mjs.map → acorex-platform-widgets-contact-widget-filter.component-CtCAgeH9.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-widgets-file-list-popup.component-yqqPJMg2.mjs → acorex-platform-widgets-file-list-popup.component-hyJf0xT2.mjs} +4 -4
- package/fesm2022/{acorex-platform-widgets-file-list-popup.component-yqqPJMg2.mjs.map → acorex-platform-widgets-file-list-popup.component-hyJf0xT2.mjs.map} +1 -1
- package/fesm2022/acorex-platform-widgets-file-rename-popup.component-BSm6Qew2.mjs +211 -0
- package/fesm2022/acorex-platform-widgets-file-rename-popup.component-BSm6Qew2.mjs.map +1 -0
- package/fesm2022/{acorex-platform-widgets-page-widget-designer.component-tITe8tiu.mjs → acorex-platform-widgets-page-widget-designer.component-Dlu4Tkou.mjs} +6 -4
- package/fesm2022/acorex-platform-widgets-page-widget-designer.component-Dlu4Tkou.mjs.map +1 -0
- package/fesm2022/{acorex-platform-widgets-rich-text-popup.component-Bk20MsqK.mjs → acorex-platform-widgets-rich-text-popup.component-DB2IMaLw.mjs} +4 -4
- package/fesm2022/{acorex-platform-widgets-rich-text-popup.component-Bk20MsqK.mjs.map → acorex-platform-widgets-rich-text-popup.component-DB2IMaLw.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-widgets-tabular-data-edit-popup.component-2srL34Aq.mjs → acorex-platform-widgets-tabular-data-edit-popup.component-CBOUSkL4.mjs} +4 -4
- package/fesm2022/{acorex-platform-widgets-tabular-data-edit-popup.component-2srL34Aq.mjs.map → acorex-platform-widgets-tabular-data-edit-popup.component-CBOUSkL4.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-widgets-tabular-data-view-popup.component-D9654sA3.mjs → acorex-platform-widgets-tabular-data-view-popup.component-Dl5U2HFR.mjs} +4 -4
- package/fesm2022/{acorex-platform-widgets-tabular-data-view-popup.component-D9654sA3.mjs.map → acorex-platform-widgets-tabular-data-view-popup.component-Dl5U2HFR.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-widgets-text-block-widget-designer.component-C1VgsL1j.mjs → acorex-platform-widgets-text-block-widget-designer.component-CG_2APU3.mjs} +4 -4
- package/fesm2022/{acorex-platform-widgets-text-block-widget-designer.component-C1VgsL1j.mjs.map → acorex-platform-widgets-text-block-widget-designer.component-CG_2APU3.mjs.map} +1 -1
- package/fesm2022/acorex-platform-widgets.mjs +942 -706
- package/fesm2022/acorex-platform-widgets.mjs.map +1 -1
- package/fesm2022/acorex-platform-workflow.mjs +25 -25
- package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
- package/layout/builder/index.d.ts +21 -13
- package/layout/components/index.d.ts +106 -2
- package/layout/entity/index.d.ts +9 -0
- package/layout/views/index.d.ts +83 -24
- package/package.json +1 -1
- package/runtime/index.d.ts +5 -5
- package/widgets/index.d.ts +16 -4
- package/domain/README.md +0 -3
- package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-BxdQ5JWS.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-D99jLndX.mjs.map +0 -1
- package/fesm2022/acorex-platform-widgets-checkbox-widget-designer.component-5kuroU6r.mjs.map +0 -1
- package/fesm2022/acorex-platform-widgets-page-widget-designer.component-tITe8tiu.mjs.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, makeEnvironmentProviders, inject, NgModule } from '@angular/core';
|
|
2
|
+
import { InjectionToken, makeEnvironmentProviders, inject, NgModule, Injectable } from '@angular/core';
|
|
3
3
|
import { AXMEntityCrudServiceImpl } from '@acorex/platform/layout/entity';
|
|
4
|
-
import { AXPCommandRegistry, AXPRuntimeModule } from '@acorex/platform/runtime';
|
|
4
|
+
import { AXPCommandRegistry, AXPQueryRegistry, AXPRuntimeModule } from '@acorex/platform/runtime';
|
|
5
5
|
import { AXP_PERMISSION_DEFINITION_PROVIDER } from '@acorex/platform/auth';
|
|
6
6
|
|
|
7
7
|
const AXP_ENTITY_CRUD_SETUP = new InjectionToken('AXP_ENTITY_CRUD_SETUP');
|
|
@@ -11,21 +11,39 @@ function provideEntity(entityKeys) {
|
|
|
11
11
|
provide: AXP_ENTITY_CRUD_SETUP,
|
|
12
12
|
multi: true,
|
|
13
13
|
useFactory: () => {
|
|
14
|
-
const
|
|
14
|
+
const commandRegistry = inject(AXPCommandRegistry);
|
|
15
|
+
const queryRegistry = inject(AXPQueryRegistry);
|
|
15
16
|
for (const entityKey of entityKeys) {
|
|
16
17
|
const service = new AXMEntityCrudServiceImpl(entityKey);
|
|
17
|
-
|
|
18
|
+
// Register Commands
|
|
19
|
+
commandRegistry.register(`${entityKey}:Create`, async () => ({
|
|
18
20
|
execute: async (input) => {
|
|
19
21
|
const id = await service.insertOne(input);
|
|
20
22
|
return service.getOne(id);
|
|
21
23
|
}
|
|
22
24
|
}));
|
|
23
|
-
|
|
25
|
+
commandRegistry.register(`${entityKey}:Update`, async () => ({
|
|
24
26
|
execute: ({ id, ...rest }) => service.updateOne(id, rest)
|
|
25
27
|
}));
|
|
26
|
-
|
|
28
|
+
commandRegistry.register(`${entityKey}:Delete`, async () => ({
|
|
27
29
|
execute: (id) => service.deleteOne(id)
|
|
28
30
|
}));
|
|
31
|
+
// Register Queries
|
|
32
|
+
queryRegistry.register(`${entityKey}:GetById`, async () => ({
|
|
33
|
+
fetch: (id) => service.getOne(id)
|
|
34
|
+
}));
|
|
35
|
+
queryRegistry.register(`${entityKey}:GetList`, async () => ({
|
|
36
|
+
fetch: (request) => service.query(request)
|
|
37
|
+
}));
|
|
38
|
+
queryRegistry.register(`${entityKey}:GetRoots`, async () => ({
|
|
39
|
+
fetch: (request) => service.getRoots(request)
|
|
40
|
+
}));
|
|
41
|
+
queryRegistry.register(`${entityKey}:GetChildren`, async () => ({
|
|
42
|
+
fetch: (request) => service.getChildren(request)
|
|
43
|
+
}));
|
|
44
|
+
queryRegistry.register(`${entityKey}:GetByCategory`, async () => ({
|
|
45
|
+
fetch: (request) => service.getByCategory(request)
|
|
46
|
+
}));
|
|
29
47
|
}
|
|
30
48
|
return true;
|
|
31
49
|
}
|
|
@@ -51,25 +69,115 @@ function provideEntity(entityKeys) {
|
|
|
51
69
|
return provider;
|
|
52
70
|
}
|
|
53
71
|
},
|
|
54
|
-
{
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
72
|
+
// {
|
|
73
|
+
// provide: AXP_ENTITY_CRUD_SETUP,
|
|
74
|
+
// multi: true,
|
|
75
|
+
// useFactory: () => {
|
|
76
|
+
// return true;
|
|
77
|
+
// }
|
|
78
|
+
// }
|
|
61
79
|
]);
|
|
62
80
|
}
|
|
63
81
|
|
|
82
|
+
//#region ---- Dependency Injection Tokens ----
|
|
83
|
+
/**
|
|
84
|
+
* Injection token for schema-specific middleware extensions.
|
|
85
|
+
*
|
|
86
|
+
* Used for targeted middleware that applies only to schemas matching
|
|
87
|
+
* specific patterns (name or regex). This enables fine-grained control
|
|
88
|
+
* over which schemas receive which middleware.
|
|
89
|
+
*/
|
|
90
|
+
const AXP_SCHEMA_EXTENSION = new InjectionToken('AXP_SCHEMA_EXTENSION');
|
|
91
|
+
/**
|
|
92
|
+
* Injection token for schema setup initialization.
|
|
93
|
+
*
|
|
94
|
+
* Used during application bootstrap to register schemas that are
|
|
95
|
+
* known at build time. Multiple providers can use this token to
|
|
96
|
+
* contribute schemas to the registry.
|
|
97
|
+
*/
|
|
98
|
+
const AXP_SCHEMA_SETUP = new InjectionToken('AXP_SCHEMA_SETUP');
|
|
99
|
+
/**
|
|
100
|
+
* Injection token for schema middleware setup initialization.
|
|
101
|
+
*
|
|
102
|
+
* Used during application bootstrap to register global middleware
|
|
103
|
+
* that applies to all schema resolutions. This enables centralized
|
|
104
|
+
* schema processing logic.
|
|
105
|
+
*/
|
|
106
|
+
const AXP_SCHEMA_MIDDLEWARE_SETUP = new InjectionToken('AXP_SCHEMA_MIDDLEWARE_SETUP');
|
|
107
|
+
/**
|
|
108
|
+
* Injection token for schema loader setup initialization.
|
|
109
|
+
*
|
|
110
|
+
* Used during application bootstrap to register schema loaders
|
|
111
|
+
* that can provide schemas on-demand when they're not found in
|
|
112
|
+
* the registry.
|
|
113
|
+
*/
|
|
114
|
+
const AXP_SCHEMA_LOADER_SETUP = new InjectionToken('AXP_SCHEMA_LOADER_SETUP');
|
|
115
|
+
//#endregion
|
|
116
|
+
|
|
117
|
+
//#region ---- Dependency Injection Tokens ----
|
|
118
|
+
/**
|
|
119
|
+
* Injection token for schema-specific middleware extensions.
|
|
120
|
+
*
|
|
121
|
+
* Used for targeted middleware that applies only to schemas matching
|
|
122
|
+
* specific patterns (name or regex). This enables fine-grained control
|
|
123
|
+
* over which schemas receive which middleware.
|
|
124
|
+
*/
|
|
125
|
+
const AXP_DOMAIN_EXTENSION = new InjectionToken('AXP_DOMAIN_EXTENSION');
|
|
126
|
+
/**
|
|
127
|
+
* Injection token for schema setup initialization.
|
|
128
|
+
*
|
|
129
|
+
* Used during application bootstrap to register schemas that are
|
|
130
|
+
* known at build time. Multiple providers can use this token to
|
|
131
|
+
* contribute schemas to the registry.
|
|
132
|
+
*/
|
|
133
|
+
const AXP_DOMAIN_SETUP = new InjectionToken('AXP_DOMAIN_SETUP');
|
|
134
|
+
/**
|
|
135
|
+
* Injection token for schema middleware setup initialization.
|
|
136
|
+
*
|
|
137
|
+
* Used during application bootstrap to register global middleware
|
|
138
|
+
* that applies to all schema resolutions. This enables centralized
|
|
139
|
+
* schema processing logic.
|
|
140
|
+
*/
|
|
141
|
+
const AXP_DOMAIN_MIDDLEWARE_SETUP = new InjectionToken('AXP_DOMAIN_MIDDLEWARE_SETUP');
|
|
142
|
+
/**
|
|
143
|
+
* Injection token for schema loader setup initialization.
|
|
144
|
+
*
|
|
145
|
+
* Used during application bootstrap to register schema loaders
|
|
146
|
+
* that can provide schemas on-demand when they're not found in
|
|
147
|
+
* the registry.
|
|
148
|
+
*/
|
|
149
|
+
const AXP_DOMAIN_LOADER_SETUP = new InjectionToken('AXP_DOMAIN_LOADER_SETUP');
|
|
150
|
+
//#endregion
|
|
151
|
+
|
|
64
152
|
class AXPDomainModule {
|
|
65
153
|
constructor() {
|
|
66
154
|
this._commandSetup = inject(AXP_ENTITY_CRUD_SETUP, { optional: true });
|
|
155
|
+
/**
|
|
156
|
+
* Injection token for schema setup initialization.
|
|
157
|
+
*
|
|
158
|
+
* Used during application bootstrap to register global middleware
|
|
159
|
+
* that applies to all schema resolutions. This enables centralized
|
|
160
|
+
* schema processing logic.
|
|
161
|
+
*/
|
|
162
|
+
this._schemaSetup = inject(AXP_SCHEMA_SETUP, { optional: true });
|
|
163
|
+
this._schemaMiddlewareSetup = inject(AXP_SCHEMA_MIDDLEWARE_SETUP, { optional: true });
|
|
164
|
+
this._schemaLoaderSetup = inject(AXP_SCHEMA_LOADER_SETUP, { optional: true });
|
|
165
|
+
/**
|
|
166
|
+
* Injection token for domain loader setup initialization.
|
|
167
|
+
*
|
|
168
|
+
* Used during application bootstrap to register domain loaders
|
|
169
|
+
* that can provide domain definitions on-demand when they're not found in
|
|
170
|
+
* the registry.
|
|
171
|
+
*/
|
|
172
|
+
this._domainLoaderSetup = inject(AXP_DOMAIN_LOADER_SETUP, { optional: true });
|
|
173
|
+
this._domainMiddlewareSetup = inject(AXP_DOMAIN_MIDDLEWARE_SETUP, { optional: true });
|
|
174
|
+
this._domainSetup = inject(AXP_DOMAIN_SETUP, { optional: true });
|
|
67
175
|
}
|
|
68
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.
|
|
69
|
-
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.0.
|
|
70
|
-
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.0.
|
|
176
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AXPDomainModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
177
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.0.6", ngImport: i0, type: AXPDomainModule, imports: [AXPRuntimeModule] }); }
|
|
178
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AXPDomainModule, imports: [AXPRuntimeModule] }); }
|
|
71
179
|
}
|
|
72
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.
|
|
180
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AXPDomainModule, decorators: [{
|
|
73
181
|
type: NgModule,
|
|
74
182
|
args: [{
|
|
75
183
|
imports: [
|
|
@@ -78,6 +186,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImpor
|
|
|
78
186
|
}]
|
|
79
187
|
}] });
|
|
80
188
|
|
|
189
|
+
var AXPEntityCommandScope;
|
|
190
|
+
(function (AXPEntityCommandScope) {
|
|
191
|
+
AXPEntityCommandScope["TypeLevel"] = "typeLevel";
|
|
192
|
+
AXPEntityCommandScope["Selected"] = "selected";
|
|
193
|
+
AXPEntityCommandScope["Individual"] = "individual";
|
|
194
|
+
AXPEntityCommandScope["Section"] = "section";
|
|
195
|
+
})(AXPEntityCommandScope || (AXPEntityCommandScope = {}));
|
|
196
|
+
|
|
197
|
+
//#endregion
|
|
198
|
+
|
|
199
|
+
var AXPEntityType;
|
|
200
|
+
(function (AXPEntityType) {
|
|
201
|
+
AXPEntityType[AXPEntityType["Entity"] = 0] = "Entity";
|
|
202
|
+
AXPEntityType[AXPEntityType["AggregateRoot"] = 1] = "AggregateRoot";
|
|
203
|
+
AXPEntityType[AXPEntityType["ValueObject"] = 2] = "ValueObject";
|
|
204
|
+
})(AXPEntityType || (AXPEntityType = {}));
|
|
205
|
+
|
|
81
206
|
var AXPRelationshipKind;
|
|
82
207
|
(function (AXPRelationshipKind) {
|
|
83
208
|
AXPRelationshipKind[AXPRelationshipKind["Association"] = 0] = "Association";
|
|
@@ -90,23 +215,3205 @@ var AXPRelationshipCardinality;
|
|
|
90
215
|
AXPRelationshipCardinality[AXPRelationshipCardinality["OneToMany"] = 1] = "OneToMany";
|
|
91
216
|
AXPRelationshipCardinality[AXPRelationshipCardinality["ManyToMany"] = 2] = "ManyToMany";
|
|
92
217
|
})(AXPRelationshipCardinality || (AXPRelationshipCardinality = {}));
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
218
|
+
|
|
219
|
+
//#region ---- AXPRelationModel ----
|
|
220
|
+
/**
|
|
221
|
+
* Runtime model for relation definitions with parent references and helper methods
|
|
222
|
+
*/
|
|
223
|
+
class AXPRelationModel {
|
|
224
|
+
constructor(definition, parent) {
|
|
225
|
+
console.log('Relation Model Constructor', definition, parent);
|
|
226
|
+
this.type = definition.type;
|
|
227
|
+
this.kind = definition.kind;
|
|
228
|
+
this.target = { ...definition.target };
|
|
229
|
+
this.source = { ...definition.source };
|
|
230
|
+
this.isRequired = definition.isRequired;
|
|
231
|
+
this.parent = parent;
|
|
232
|
+
}
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region ---- Cardinality Methods ----
|
|
235
|
+
/**
|
|
236
|
+
* Check if relation is one-to-one
|
|
237
|
+
*/
|
|
238
|
+
isOneToOne() {
|
|
239
|
+
return this.type === AXPRelationshipCardinality.OneToOne;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Check if relation is one-to-many
|
|
243
|
+
*/
|
|
244
|
+
isOneToMany() {
|
|
245
|
+
return this.type === AXPRelationshipCardinality.OneToMany;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Check if relation is many-to-many
|
|
249
|
+
*/
|
|
250
|
+
isManyToMany() {
|
|
251
|
+
return this.type === AXPRelationshipCardinality.ManyToMany;
|
|
252
|
+
}
|
|
253
|
+
//#endregion
|
|
254
|
+
//#region ---- Kind Methods ----
|
|
255
|
+
/**
|
|
256
|
+
* Check if relation is association
|
|
257
|
+
*/
|
|
258
|
+
isAssociation() {
|
|
259
|
+
return this.kind === AXPRelationshipKind.Association;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Check if relation is composition
|
|
263
|
+
*/
|
|
264
|
+
isComposition() {
|
|
265
|
+
return this.kind === AXPRelationshipKind.Composition;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Check if relation is aggregation
|
|
269
|
+
*/
|
|
270
|
+
isAggregation() {
|
|
271
|
+
return this.kind === AXPRelationshipKind.Aggregation;
|
|
272
|
+
}
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region ---- Navigation Methods ----
|
|
275
|
+
/**
|
|
276
|
+
* Get parent aggregate
|
|
277
|
+
*/
|
|
278
|
+
getAggregate() {
|
|
279
|
+
return this.parent;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Get parent module
|
|
283
|
+
*/
|
|
284
|
+
getModule() {
|
|
285
|
+
return this.parent?.parent;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Get full path (module.aggregate.relation)
|
|
289
|
+
*/
|
|
290
|
+
getPath() {
|
|
291
|
+
return `${this.parent.parent.name}.${this.parent.name}.${this.source.entity}->${this.target.entity}`;
|
|
292
|
+
}
|
|
293
|
+
//#endregion
|
|
294
|
+
//#region ---- Query Methods ----
|
|
295
|
+
/**
|
|
296
|
+
* Get relation description
|
|
297
|
+
*/
|
|
298
|
+
getDescription() {
|
|
299
|
+
const cardinalityDesc = this.getCardinalityDescription();
|
|
300
|
+
const kindDesc = this.getKindDescription();
|
|
301
|
+
const requiredDesc = this.isRequired ? 'required' : 'optional';
|
|
302
|
+
return `${cardinalityDesc} ${kindDesc} (${requiredDesc})`;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Get cardinality description
|
|
306
|
+
*/
|
|
307
|
+
getCardinalityDescription() {
|
|
308
|
+
switch (this.type) {
|
|
309
|
+
case AXPRelationshipCardinality.OneToOne:
|
|
310
|
+
return 'one-to-one';
|
|
311
|
+
case AXPRelationshipCardinality.OneToMany:
|
|
312
|
+
return 'one-to-many';
|
|
313
|
+
case AXPRelationshipCardinality.ManyToMany:
|
|
314
|
+
return 'many-to-many';
|
|
315
|
+
default:
|
|
316
|
+
return 'unknown';
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Get kind description
|
|
321
|
+
*/
|
|
322
|
+
getKindDescription() {
|
|
323
|
+
switch (this.kind) {
|
|
324
|
+
case AXPRelationshipKind.Association:
|
|
325
|
+
return 'association';
|
|
326
|
+
case AXPRelationshipKind.Composition:
|
|
327
|
+
return 'composition';
|
|
328
|
+
case AXPRelationshipKind.Aggregation:
|
|
329
|
+
return 'aggregation';
|
|
330
|
+
default:
|
|
331
|
+
return 'unknown';
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Get relation summary
|
|
336
|
+
*/
|
|
337
|
+
getSummary() {
|
|
338
|
+
return `${this.source.entity}.${this.source.key} -> ${this.target.aggregate}.${this.target.entity}.${this.target.key}`;
|
|
339
|
+
}
|
|
340
|
+
//#endregion
|
|
341
|
+
//#region ---- Validation Methods ----
|
|
342
|
+
/**
|
|
343
|
+
* Validate the relation structure
|
|
344
|
+
*/
|
|
345
|
+
validate() {
|
|
346
|
+
const errors = [];
|
|
347
|
+
// Validate source
|
|
348
|
+
if (!this.source.entity)
|
|
349
|
+
errors.push('Source entity is required');
|
|
350
|
+
if (!this.source.key)
|
|
351
|
+
errors.push('Source key is required');
|
|
352
|
+
// Validate target
|
|
353
|
+
if (!this.target.aggregate)
|
|
354
|
+
errors.push('Target aggregate is required');
|
|
355
|
+
if (!this.target.entity)
|
|
356
|
+
errors.push('Target entity is required');
|
|
357
|
+
if (!this.target.key)
|
|
358
|
+
errors.push('Target key is required');
|
|
359
|
+
return errors;
|
|
360
|
+
}
|
|
361
|
+
//#endregion
|
|
362
|
+
//#region ---- Utility Methods ----
|
|
363
|
+
/**
|
|
364
|
+
* Get relation characteristics
|
|
365
|
+
*/
|
|
366
|
+
getCharacteristics() {
|
|
367
|
+
return {
|
|
368
|
+
type: this.getCardinalityDescription(),
|
|
369
|
+
kind: this.getKindDescription(),
|
|
370
|
+
required: this.isRequired,
|
|
371
|
+
isOwning: this.isComposition(),
|
|
372
|
+
isNavigable: !this.isComposition() || this.isOneToOne()
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Check if relation involves entity
|
|
377
|
+
*/
|
|
378
|
+
involvesEntity(entityName) {
|
|
379
|
+
return this.source.entity === entityName || this.target.entity === entityName;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Check if relation targets aggregate
|
|
383
|
+
*/
|
|
384
|
+
targetsAggregate(aggregateName) {
|
|
385
|
+
return this.target.aggregate === aggregateName;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Get the other entity in the relation
|
|
389
|
+
*/
|
|
390
|
+
getOtherEntity(entityName) {
|
|
391
|
+
if (this.source.entity === entityName) {
|
|
392
|
+
return this.target.entity;
|
|
393
|
+
}
|
|
394
|
+
else if (this.target.entity === entityName) {
|
|
395
|
+
return this.source.entity;
|
|
396
|
+
}
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Convert back to interface definition
|
|
401
|
+
*/
|
|
402
|
+
toDefinition() {
|
|
403
|
+
return {
|
|
404
|
+
type: this.type,
|
|
405
|
+
kind: this.kind,
|
|
406
|
+
target: { ...this.target },
|
|
407
|
+
source: { ...this.source },
|
|
408
|
+
isRequired: this.isRequired
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
//#region ---- AXPAggregateModel ----
|
|
414
|
+
/**
|
|
415
|
+
* Simple runtime model for aggregate definitions with parent references and navigation helpers.
|
|
416
|
+
*
|
|
417
|
+
* This model is a pure data structure that provides:
|
|
418
|
+
* - Access to aggregate properties and metadata
|
|
419
|
+
* - Navigation helpers for finding relations and entity references
|
|
420
|
+
* - Parent module reference for hierarchy navigation
|
|
421
|
+
*
|
|
422
|
+
* All loading and resolution logic has been moved to AXPDomainRegistry.
|
|
423
|
+
*/
|
|
424
|
+
class AXPAggregateModel {
|
|
425
|
+
constructor(definition, parent) {
|
|
426
|
+
this.relations = [];
|
|
427
|
+
this.name = definition.name;
|
|
428
|
+
this.title = definition.title;
|
|
429
|
+
this.validations = definition.validations || [];
|
|
430
|
+
this.actions = definition.actions || [];
|
|
431
|
+
this.parent = parent;
|
|
432
|
+
// Initialize relations with parent reference
|
|
433
|
+
this.relations = (definition.relations || []).map(relationDef => new AXPRelationModel(relationDef, this));
|
|
434
|
+
// Extract entity references - convert all formats to simple string map
|
|
435
|
+
this.entityReferences = this.extractEntityReferences(definition.entities);
|
|
436
|
+
}
|
|
437
|
+
//#endregion
|
|
438
|
+
//#region ---- Entity Reference Extraction ----
|
|
439
|
+
/**
|
|
440
|
+
* Extract entity references from various entity definition formats
|
|
441
|
+
* and convert to simple string map
|
|
442
|
+
*/
|
|
443
|
+
extractEntityReferences(entities) {
|
|
444
|
+
if (!entities) {
|
|
445
|
+
return {};
|
|
446
|
+
}
|
|
447
|
+
if (Array.isArray(entities)) {
|
|
448
|
+
// Format 1: Array of entity definitions - convert to self-references
|
|
449
|
+
const references = {};
|
|
450
|
+
for (const entity of entities) {
|
|
451
|
+
if (entity.name) {
|
|
452
|
+
// Self-reference format: module.aggregate.entity
|
|
453
|
+
references[entity.name] = `${this.parent.name}.${this.name}.${entity.name}`;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return references;
|
|
457
|
+
}
|
|
458
|
+
if (typeof entities === 'object') {
|
|
459
|
+
// Format 2: Object with string references - use as-is
|
|
460
|
+
return { ...entities };
|
|
461
|
+
}
|
|
462
|
+
return {};
|
|
463
|
+
}
|
|
464
|
+
//#endregion
|
|
465
|
+
//#region ---- Navigation Helpers ----
|
|
466
|
+
/**
|
|
467
|
+
* Get entity reference by name
|
|
468
|
+
*
|
|
469
|
+
* @param entityName Name of the entity
|
|
470
|
+
* @returns Entity reference string or undefined if not found
|
|
471
|
+
*/
|
|
472
|
+
getEntityReference(entityName) {
|
|
473
|
+
return this.entityReferences[entityName];
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Get full path for an entity in this aggregate
|
|
477
|
+
*
|
|
478
|
+
* @param entityName Name of the entity
|
|
479
|
+
* @returns Full entity path
|
|
480
|
+
*/
|
|
481
|
+
getEntityPath(entityName) {
|
|
482
|
+
return `${this.parent.name}.${this.name}.${entityName}`;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Get all available entity names
|
|
486
|
+
*
|
|
487
|
+
* @returns Array of entity names
|
|
488
|
+
*/
|
|
489
|
+
getEntityNames() {
|
|
490
|
+
return Object.keys(this.entityReferences);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Check if entity exists in this aggregate
|
|
494
|
+
*
|
|
495
|
+
* @param entityName Name of the entity
|
|
496
|
+
* @returns True if entity exists
|
|
497
|
+
*/
|
|
498
|
+
hasEntity(entityName) {
|
|
499
|
+
return entityName in this.entityReferences;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Find relation by source and target entities
|
|
503
|
+
*
|
|
504
|
+
* @param sourceEntity Source entity name
|
|
505
|
+
* @param targetEntity Target entity name
|
|
506
|
+
* @returns Relation model or undefined if not found
|
|
507
|
+
*/
|
|
508
|
+
findRelation(sourceEntity, targetEntity) {
|
|
509
|
+
return this.relations.find(relation => relation.source.entity === sourceEntity &&
|
|
510
|
+
relation.target.entity === targetEntity);
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Get all relations for a specific entity
|
|
514
|
+
*
|
|
515
|
+
* @param entityName Entity name
|
|
516
|
+
* @returns Array of relations involving the entity
|
|
517
|
+
*/
|
|
518
|
+
getEntityRelations(entityName) {
|
|
519
|
+
return this.relations.filter(relation => relation.source.entity === entityName ||
|
|
520
|
+
relation.target.entity === entityName);
|
|
521
|
+
}
|
|
522
|
+
//#endregion
|
|
523
|
+
//#region ---- Parent Navigation ----
|
|
524
|
+
/**
|
|
525
|
+
* Get parent module
|
|
526
|
+
*
|
|
527
|
+
* @returns Parent module model
|
|
528
|
+
*/
|
|
529
|
+
getModule() {
|
|
530
|
+
return this.parent;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Get full aggregate path (module.aggregate)
|
|
534
|
+
*
|
|
535
|
+
* @returns Full path string
|
|
536
|
+
*/
|
|
537
|
+
getPath() {
|
|
538
|
+
return `${this.parent.name}.${this.name}`;
|
|
539
|
+
}
|
|
540
|
+
//#endregion
|
|
541
|
+
//#region ---- Utility Methods ----
|
|
542
|
+
/**
|
|
543
|
+
* Get aggregate statistics
|
|
544
|
+
*
|
|
545
|
+
* @returns Statistics object
|
|
546
|
+
*/
|
|
547
|
+
getStatistics() {
|
|
548
|
+
return {
|
|
549
|
+
entityCount: Object.keys(this.entityReferences).length,
|
|
550
|
+
relationCount: this.relations.length,
|
|
551
|
+
actionCount: this.actions.length,
|
|
552
|
+
validationCount: this.validations.length
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Convert back to interface definition
|
|
557
|
+
*
|
|
558
|
+
* @returns Aggregate definition
|
|
559
|
+
*/
|
|
560
|
+
toDefinition() {
|
|
561
|
+
return {
|
|
562
|
+
name: this.name,
|
|
563
|
+
title: this.title,
|
|
564
|
+
entities: this.entityReferences, // Return as reference map
|
|
565
|
+
relations: this.relations.map(relation => relation.toDefinition()),
|
|
566
|
+
validations: this.validations,
|
|
567
|
+
actions: this.actions
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
//#endregion
|
|
571
|
+
//#region ---- Validation Methods ----
|
|
572
|
+
/**
|
|
573
|
+
* Validate the aggregate structure (synchronous validation only)
|
|
574
|
+
*
|
|
575
|
+
* @returns Array of validation errors
|
|
576
|
+
*/
|
|
577
|
+
validate() {
|
|
578
|
+
const errors = [];
|
|
579
|
+
// Validate aggregate structure
|
|
580
|
+
if (!this.name)
|
|
581
|
+
errors.push('Aggregate name is required');
|
|
582
|
+
if (!this.title)
|
|
583
|
+
errors.push('Aggregate title is required');
|
|
584
|
+
// Validate relations
|
|
585
|
+
this.relations.forEach(relation => {
|
|
586
|
+
const relationErrors = relation.validate();
|
|
587
|
+
if (relationErrors.length > 0) {
|
|
588
|
+
errors.push(...relationErrors.map(err => `Relation: ${err}`));
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
// Validate entity references
|
|
592
|
+
for (const [entityName, entityRef] of Object.entries(this.entityReferences)) {
|
|
593
|
+
if (!entityRef || typeof entityRef !== 'string') {
|
|
594
|
+
errors.push(`Invalid entity reference for '${entityName}': must be a non-empty string`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return errors;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
//#endregion
|
|
601
|
+
|
|
602
|
+
//#region ---- AXPModuleModel ----
|
|
603
|
+
/**
|
|
604
|
+
* Simple runtime model for module definitions with navigation helpers.
|
|
605
|
+
*
|
|
606
|
+
* This model is a pure data structure that provides:
|
|
607
|
+
* - Access to module properties and metadata
|
|
608
|
+
* - Navigation helpers for finding aggregates
|
|
609
|
+
* - Hierarchy navigation and path generation
|
|
610
|
+
*
|
|
611
|
+
* All loading and resolution logic has been moved to AXPDomainRegistry.
|
|
612
|
+
*/
|
|
613
|
+
class AXPModuleModel {
|
|
614
|
+
constructor(definition) {
|
|
615
|
+
this.aggregates = [];
|
|
616
|
+
this.name = definition.name;
|
|
617
|
+
this.title = definition.title;
|
|
618
|
+
// Initialize aggregates with parent reference (simplified constructor)
|
|
619
|
+
this.aggregates = (definition.aggregates || []).map(aggDef => new AXPAggregateModel(aggDef, this));
|
|
620
|
+
}
|
|
621
|
+
//#endregion
|
|
622
|
+
//#region ---- Navigation Helpers ----
|
|
623
|
+
/**
|
|
624
|
+
* Find aggregate by name
|
|
625
|
+
*
|
|
626
|
+
* @param name Aggregate name
|
|
627
|
+
* @returns Aggregate model or undefined if not found
|
|
628
|
+
*/
|
|
629
|
+
findAggregate(name) {
|
|
630
|
+
return this.aggregates.find(agg => agg.name === name);
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Get all aggregates
|
|
634
|
+
*
|
|
635
|
+
* @returns Array of all aggregate models
|
|
636
|
+
*/
|
|
637
|
+
getAllAggregates() {
|
|
638
|
+
return [...this.aggregates];
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Check if aggregate exists in this module
|
|
642
|
+
*
|
|
643
|
+
* @param name Aggregate name
|
|
644
|
+
* @returns True if aggregate exists
|
|
645
|
+
*/
|
|
646
|
+
hasAggregate(name) {
|
|
647
|
+
return this.aggregates.some(agg => agg.name === name);
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Get all aggregate names
|
|
651
|
+
*
|
|
652
|
+
* @returns Array of aggregate names
|
|
653
|
+
*/
|
|
654
|
+
getAggregateNames() {
|
|
655
|
+
return this.aggregates.map(agg => agg.name);
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Find entity reference across all aggregates
|
|
659
|
+
*
|
|
660
|
+
* @param entityName Entity name to search for
|
|
661
|
+
* @returns Object with aggregate and entity reference, or undefined if not found
|
|
662
|
+
*/
|
|
663
|
+
findEntityReference(entityName) {
|
|
664
|
+
for (const aggregate of this.aggregates) {
|
|
665
|
+
const entityReference = aggregate.getEntityReference(entityName);
|
|
666
|
+
if (entityReference) {
|
|
667
|
+
return { aggregate, entityReference };
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return undefined;
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Get entity path for a specific entity in a specific aggregate
|
|
674
|
+
*
|
|
675
|
+
* @param aggregateName Aggregate name
|
|
676
|
+
* @param entityName Entity name
|
|
677
|
+
* @returns Full entity path
|
|
678
|
+
*/
|
|
679
|
+
getEntityPath(aggregateName, entityName) {
|
|
680
|
+
return `${this.name}.${aggregateName}.${entityName}`;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Get aggregate path
|
|
684
|
+
*
|
|
685
|
+
* @param aggregateName Aggregate name
|
|
686
|
+
* @returns Full aggregate path
|
|
687
|
+
*/
|
|
688
|
+
getAggregatePath(aggregateName) {
|
|
689
|
+
return `${this.name}.${aggregateName}`;
|
|
690
|
+
}
|
|
691
|
+
//#endregion
|
|
692
|
+
//#region ---- Query Methods ----
|
|
693
|
+
/**
|
|
694
|
+
* Get all entity references across all aggregates
|
|
695
|
+
*
|
|
696
|
+
* @returns Map of entity name to full path
|
|
697
|
+
*/
|
|
698
|
+
getAllEntityReferences() {
|
|
699
|
+
const allReferences = {};
|
|
700
|
+
for (const aggregate of this.aggregates) {
|
|
701
|
+
const aggregateReferences = aggregate.entityReferences;
|
|
702
|
+
for (const [entityName, entityRef] of Object.entries(aggregateReferences)) {
|
|
703
|
+
// Use full path as key to avoid conflicts
|
|
704
|
+
const fullKey = `${aggregate.name}.${entityName}`;
|
|
705
|
+
allReferences[fullKey] = entityRef;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return allReferences;
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Get all relations across all aggregates
|
|
712
|
+
*
|
|
713
|
+
* @returns Array of all relation models
|
|
714
|
+
*/
|
|
715
|
+
getAllRelations() {
|
|
716
|
+
const allRelations = [];
|
|
717
|
+
for (const aggregate of this.aggregates) {
|
|
718
|
+
for (const relation of aggregate.relations) {
|
|
719
|
+
allRelations.push({ aggregate, relation });
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return allRelations;
|
|
723
|
+
}
|
|
724
|
+
//#endregion
|
|
725
|
+
//#region ---- Validation Methods ----
|
|
726
|
+
/**
|
|
727
|
+
* Validate the module structure (synchronous validation only)
|
|
728
|
+
*
|
|
729
|
+
* @returns Array of validation errors
|
|
730
|
+
*/
|
|
731
|
+
validate() {
|
|
732
|
+
const errors = [];
|
|
733
|
+
// Validate module structure
|
|
734
|
+
if (!this.name)
|
|
735
|
+
errors.push('Module name is required');
|
|
736
|
+
if (!this.title)
|
|
737
|
+
errors.push('Module title is required');
|
|
738
|
+
// Check for duplicate aggregate names
|
|
739
|
+
const aggregateNames = this.aggregates.map(agg => agg.name);
|
|
740
|
+
const duplicateNames = aggregateNames.filter((name, index) => aggregateNames.indexOf(name) !== index);
|
|
741
|
+
if (duplicateNames.length > 0) {
|
|
742
|
+
errors.push(`Duplicate aggregate names: ${duplicateNames.join(', ')}`);
|
|
743
|
+
}
|
|
744
|
+
// Validate aggregates
|
|
745
|
+
for (const aggregate of this.aggregates) {
|
|
746
|
+
const aggErrors = aggregate.validate();
|
|
747
|
+
if (aggErrors.length > 0) {
|
|
748
|
+
errors.push(...aggErrors.map((err) => `${aggregate.name}: ${err}`));
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return errors;
|
|
752
|
+
}
|
|
753
|
+
//#endregion
|
|
754
|
+
//#region ---- Utility Methods ----
|
|
755
|
+
/**
|
|
756
|
+
* Get module statistics
|
|
757
|
+
*
|
|
758
|
+
* @returns Statistics object
|
|
759
|
+
*/
|
|
760
|
+
getStatistics() {
|
|
761
|
+
const aggregateStats = this.aggregates.map(agg => agg.getStatistics());
|
|
762
|
+
return {
|
|
763
|
+
aggregateCount: this.aggregates.length,
|
|
764
|
+
entityCount: aggregateStats.reduce((sum, stat) => sum + stat.entityCount, 0),
|
|
765
|
+
relationCount: aggregateStats.reduce((sum, stat) => sum + stat.relationCount, 0),
|
|
766
|
+
actionCount: aggregateStats.reduce((sum, stat) => sum + stat.actionCount, 0),
|
|
767
|
+
validationCount: aggregateStats.reduce((sum, stat) => sum + stat.validationCount, 0)
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Convert back to interface definition
|
|
772
|
+
*
|
|
773
|
+
* @returns Module definition
|
|
774
|
+
*/
|
|
775
|
+
toDefinition() {
|
|
776
|
+
return {
|
|
777
|
+
name: this.name,
|
|
778
|
+
title: this.title,
|
|
779
|
+
aggregates: this.aggregates.map(agg => agg.toDefinition())
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
//#endregion
|
|
784
|
+
|
|
785
|
+
//#region ---- AXPEntityFieldModel ----
|
|
786
|
+
/**
|
|
787
|
+
* Runtime model for entity field definitions with parent references and helper methods
|
|
788
|
+
*/
|
|
789
|
+
class AXPEntityFieldModel {
|
|
790
|
+
constructor(definition, parent, schemaService) {
|
|
791
|
+
this.name = definition.name;
|
|
792
|
+
this.title = definition.title;
|
|
793
|
+
this.description = definition.description;
|
|
794
|
+
this.validations = definition.validations;
|
|
795
|
+
this.actions = definition.actions;
|
|
796
|
+
this.features = definition.features;
|
|
797
|
+
this.defaultValue = definition.defaultValue;
|
|
798
|
+
this.parent = parent;
|
|
799
|
+
this.schemaService = schemaService;
|
|
800
|
+
// Store schema name for toDefinition
|
|
801
|
+
this.schemaName = definition.schema;
|
|
802
|
+
// Resolve schema by name from schema service
|
|
803
|
+
this.schema = schemaService.resolveSync(definition.schema);
|
|
804
|
+
}
|
|
805
|
+
//#endregion
|
|
806
|
+
//#region ---- Feature Check Methods ----
|
|
807
|
+
/**
|
|
808
|
+
* Check if field is nullable
|
|
809
|
+
*/
|
|
810
|
+
isNullable() {
|
|
811
|
+
return this.features?.nullable ?? false;
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Check if field is readonly
|
|
815
|
+
*/
|
|
816
|
+
isReadonly() {
|
|
817
|
+
return this.features?.readOnly ?? false;
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Check if field is required (has required validation)
|
|
821
|
+
*/
|
|
822
|
+
isRequired() {
|
|
823
|
+
return this.validations?.some(validation => validation.rule === 'required') ?? false;
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Check if field is searchable
|
|
827
|
+
*/
|
|
828
|
+
isSearchable() {
|
|
829
|
+
return this.schema.features?.searchable?.enabled ?? false;
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Check if field supports full text search
|
|
833
|
+
*/
|
|
834
|
+
isFullTextSearchable() {
|
|
835
|
+
return this.schema.features?.searchable?.fullText ?? false;
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Check if field is filterable
|
|
839
|
+
*/
|
|
840
|
+
isFilterable() {
|
|
841
|
+
return this.schema.features?.filterable?.enabled ?? false;
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Check if field has inline filtering
|
|
845
|
+
*/
|
|
846
|
+
hasInlineFiltering() {
|
|
847
|
+
return this.schema.features?.filterable?.inline ?? false;
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Check if field is sortable
|
|
851
|
+
*/
|
|
852
|
+
isSortable() {
|
|
853
|
+
return this.schema.features?.sortable?.enabled ?? false;
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Check if field is auditable
|
|
857
|
+
*/
|
|
858
|
+
isAuditable() {
|
|
859
|
+
return this.schema.features?.auditable?.enabled ?? false;
|
|
860
|
+
}
|
|
861
|
+
//#endregion
|
|
862
|
+
//#region ---- Schema Methods ----
|
|
863
|
+
/**
|
|
864
|
+
* Get the schema name
|
|
865
|
+
*/
|
|
866
|
+
getSchemaName() {
|
|
867
|
+
return this.schemaName;
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Get schema info from registry
|
|
871
|
+
*/
|
|
872
|
+
getSchemaInfo() {
|
|
873
|
+
return this.schemaService.getSchemaInfo(this.schemaName);
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Re-resolve schema from registry (useful after schema updates)
|
|
877
|
+
*/
|
|
878
|
+
refreshSchema() {
|
|
879
|
+
const newSchema = this.schemaService.resolveSync(this.schemaName);
|
|
880
|
+
this.schema = newSchema;
|
|
881
|
+
this.schema.parent = this;
|
|
882
|
+
}
|
|
883
|
+
//#endregion
|
|
884
|
+
//#region ---- Navigation Methods ----
|
|
885
|
+
/**
|
|
886
|
+
* Get parent entity
|
|
887
|
+
*/
|
|
888
|
+
getEntity() {
|
|
889
|
+
return this.parent;
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Get parent aggregate
|
|
893
|
+
*/
|
|
894
|
+
getAggregate() {
|
|
895
|
+
return this.parent?.parent;
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Get parent module
|
|
899
|
+
*/
|
|
900
|
+
getModule() {
|
|
901
|
+
return this.parent?.parent?.parent;
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Get full path (module.aggregate.entity.field)
|
|
905
|
+
*/
|
|
906
|
+
getPath() {
|
|
907
|
+
return `${this.parent.parent.parent.name}.${this.parent.parent.name}.${this.parent.name}.${this.name}`;
|
|
908
|
+
}
|
|
909
|
+
//#endregion
|
|
910
|
+
//#region ---- Validation Methods ----
|
|
911
|
+
/**
|
|
912
|
+
* Validate the field structure
|
|
913
|
+
*/
|
|
914
|
+
async validate() {
|
|
915
|
+
const errors = [];
|
|
916
|
+
// Validate field structure
|
|
917
|
+
if (!this.name)
|
|
918
|
+
errors.push('Field name is required');
|
|
919
|
+
if (!this.title)
|
|
920
|
+
errors.push('Field title is required');
|
|
921
|
+
// Validate schema name
|
|
922
|
+
if (!this.schemaName) {
|
|
923
|
+
errors.push('Schema name is required');
|
|
924
|
+
}
|
|
925
|
+
else {
|
|
926
|
+
if (!this.schemaService.isRegistered(this.schemaName)) {
|
|
927
|
+
errors.push(`Schema '${this.schemaName}' is not registered`);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
// Validate schema if it exists
|
|
931
|
+
if (this.schema) {
|
|
932
|
+
const schemaErrors = await this.schema.validate();
|
|
933
|
+
if (schemaErrors.length > 0) {
|
|
934
|
+
errors.push(...schemaErrors.map(err => `Schema: ${err}`));
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return errors;
|
|
938
|
+
}
|
|
939
|
+
//#endregion
|
|
940
|
+
//#region ---- Utility Methods ----
|
|
941
|
+
/**
|
|
942
|
+
* Get field statistics
|
|
943
|
+
*/
|
|
944
|
+
getStatistics() {
|
|
945
|
+
return {
|
|
946
|
+
hasValidations: !!this.validations && this.validations.length > 0,
|
|
947
|
+
hasActions: !!this.actions && this.actions.length > 0,
|
|
948
|
+
hasFeatures: !!this.features,
|
|
949
|
+
hasDefaultValue: this.defaultValue !== undefined,
|
|
950
|
+
validationCount: this.validations?.length ?? 0,
|
|
951
|
+
actionCount: this.actions?.length ?? 0
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Get field capabilities
|
|
956
|
+
*/
|
|
957
|
+
getCapabilities() {
|
|
958
|
+
return {
|
|
959
|
+
searchable: this.isSearchable(),
|
|
960
|
+
filterable: this.isFilterable(),
|
|
961
|
+
sortable: this.isSortable(),
|
|
962
|
+
auditable: this.isAuditable(),
|
|
963
|
+
nullable: this.isNullable(),
|
|
964
|
+
readonly: this.isReadonly(),
|
|
965
|
+
required: this.isRequired()
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Convert back to interface definition
|
|
970
|
+
*/
|
|
971
|
+
toDefinition() {
|
|
972
|
+
return {
|
|
973
|
+
name: this.name,
|
|
974
|
+
title: this.title,
|
|
975
|
+
description: this.description,
|
|
976
|
+
validations: this.validations,
|
|
977
|
+
actions: this.actions,
|
|
978
|
+
features: this.features,
|
|
979
|
+
defaultValue: this.defaultValue,
|
|
980
|
+
schema: this.schemaName
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
//#region ---- AXPEntityModel ----
|
|
986
|
+
/**
|
|
987
|
+
* Runtime model for entity definitions with parent references and helper methods
|
|
988
|
+
*/
|
|
989
|
+
class AXPEntityModel {
|
|
990
|
+
constructor(definition, parent, schemaService) {
|
|
991
|
+
this.fields = [];
|
|
992
|
+
this.name = definition.name;
|
|
993
|
+
this.title = definition.title;
|
|
994
|
+
this.type = definition.type;
|
|
995
|
+
this.parent = parent;
|
|
996
|
+
this.schemaService = schemaService;
|
|
997
|
+
// Initialize fields with parent reference and schema service
|
|
998
|
+
this.fields = definition.fields.map(fieldDef => new AXPEntityFieldModel(fieldDef, this, schemaService));
|
|
999
|
+
}
|
|
1000
|
+
//#endregion
|
|
1001
|
+
//#region ---- Query Methods ----
|
|
1002
|
+
/**
|
|
1003
|
+
* Find field by name
|
|
1004
|
+
*/
|
|
1005
|
+
findField(name) {
|
|
1006
|
+
return this.fields.find(field => field.name === name);
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Get all fields
|
|
1010
|
+
*/
|
|
1011
|
+
getAllFields() {
|
|
1012
|
+
return [...this.fields];
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Get fields by data type
|
|
1016
|
+
*/
|
|
1017
|
+
getFieldsByType(dataType) {
|
|
1018
|
+
return this.fields.filter(field => field.schema.dataType === dataType);
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Get required fields
|
|
1022
|
+
*/
|
|
1023
|
+
getRequiredFields() {
|
|
1024
|
+
return this.fields.filter(field => field.isRequired());
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Get readonly fields
|
|
1028
|
+
*/
|
|
1029
|
+
getReadonlyFields() {
|
|
1030
|
+
return this.fields.filter(field => field.isReadonly());
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Get searchable fields
|
|
1034
|
+
*/
|
|
1035
|
+
getSearchableFields() {
|
|
1036
|
+
return this.fields.filter(field => field.isSearchable());
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Get filterable fields
|
|
1040
|
+
*/
|
|
1041
|
+
getFilterableFields() {
|
|
1042
|
+
return this.fields.filter(field => field.isFilterable());
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Get sortable fields
|
|
1046
|
+
*/
|
|
1047
|
+
getSortableFields() {
|
|
1048
|
+
return this.fields.filter(field => field.isSortable());
|
|
1049
|
+
}
|
|
1050
|
+
//#endregion
|
|
1051
|
+
//#region ---- Type Checking Methods ----
|
|
1052
|
+
/**
|
|
1053
|
+
* Check if this entity is an aggregate root
|
|
1054
|
+
*/
|
|
1055
|
+
isAggregateRoot() {
|
|
1056
|
+
return this.type === AXPEntityType.AggregateRoot;
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Check if this entity is a regular entity
|
|
1060
|
+
*/
|
|
1061
|
+
isEntity() {
|
|
1062
|
+
return this.type === AXPEntityType.Entity;
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Check if this entity is a value object
|
|
1066
|
+
*/
|
|
1067
|
+
isValueObject() {
|
|
1068
|
+
return this.type === AXPEntityType.ValueObject;
|
|
1069
|
+
}
|
|
1070
|
+
//#endregion
|
|
1071
|
+
//#region ---- Navigation Methods ----
|
|
1072
|
+
/**
|
|
1073
|
+
* Get parent aggregate
|
|
1074
|
+
*/
|
|
1075
|
+
getAggregate() {
|
|
1076
|
+
return this.parent;
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Get parent module
|
|
1080
|
+
*/
|
|
1081
|
+
getModule() {
|
|
1082
|
+
return this.parent?.parent;
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Get full path (module.aggregate.entity)
|
|
1086
|
+
*/
|
|
1087
|
+
getPath() {
|
|
1088
|
+
return `${this.parent.parent.name}.${this.parent.name}.${this.name}`;
|
|
1089
|
+
}
|
|
1090
|
+
//#endregion
|
|
1091
|
+
//#region ---- Validation Methods ----
|
|
1092
|
+
/**
|
|
1093
|
+
* Validate the entity structure
|
|
1094
|
+
*/
|
|
1095
|
+
async validate() {
|
|
1096
|
+
const errors = [];
|
|
1097
|
+
// Validate entity structure
|
|
1098
|
+
if (!this.name)
|
|
1099
|
+
errors.push('Entity name is required');
|
|
1100
|
+
if (!this.title)
|
|
1101
|
+
errors.push('Entity title is required');
|
|
1102
|
+
// Validate fields
|
|
1103
|
+
for (const field of this.fields) {
|
|
1104
|
+
const fieldErrors = await field.validate();
|
|
1105
|
+
if (fieldErrors.length > 0) {
|
|
1106
|
+
errors.push(...fieldErrors.map(err => `${field.name}: ${err}`));
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
return errors;
|
|
1110
|
+
}
|
|
1111
|
+
//#endregion
|
|
1112
|
+
//#region ---- Utility Methods ----
|
|
1113
|
+
/**
|
|
1114
|
+
* Get entity statistics
|
|
1115
|
+
*/
|
|
1116
|
+
getStatistics() {
|
|
1117
|
+
return {
|
|
1118
|
+
fieldCount: this.fields.length,
|
|
1119
|
+
requiredFieldCount: this.getRequiredFields().length,
|
|
1120
|
+
readonlyFieldCount: this.getReadonlyFields().length,
|
|
1121
|
+
searchableFieldCount: this.getSearchableFields().length,
|
|
1122
|
+
filterableFieldCount: this.getFilterableFields().length,
|
|
1123
|
+
sortableFieldCount: this.getSortableFields().length
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Convert back to interface definition
|
|
1128
|
+
*/
|
|
1129
|
+
toDefinition() {
|
|
1130
|
+
return {
|
|
1131
|
+
name: this.name,
|
|
1132
|
+
title: this.title,
|
|
1133
|
+
fields: this.fields.map(field => field.toDefinition()),
|
|
1134
|
+
type: this.type
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
//#region ---- AXPSchemaModel ----
|
|
1140
|
+
/**
|
|
1141
|
+
* Runtime model for schema definitions with helper methods
|
|
1142
|
+
*/
|
|
1143
|
+
class AXPSchemaModel {
|
|
1144
|
+
constructor(definition) {
|
|
1145
|
+
this.name = definition.name;
|
|
1146
|
+
this.dataType = definition.dataType;
|
|
1147
|
+
this.interface = definition.interface;
|
|
1148
|
+
this.validations = definition.validations ?? [];
|
|
1149
|
+
this.features = definition.features ?? {};
|
|
1150
|
+
this.metadata = definition.metadata;
|
|
1151
|
+
}
|
|
1152
|
+
//#endregion
|
|
1153
|
+
//#region ---- Data Type Methods ----
|
|
1154
|
+
/**
|
|
1155
|
+
* Check if schema is for string data
|
|
1156
|
+
*/
|
|
1157
|
+
isString() {
|
|
1158
|
+
return this.dataType === 'string';
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Check if schema is for number data
|
|
1162
|
+
*/
|
|
1163
|
+
isNumber() {
|
|
1164
|
+
return this.dataType === 'number';
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Check if schema is for boolean data
|
|
1168
|
+
*/
|
|
1169
|
+
isBoolean() {
|
|
1170
|
+
return this.dataType === 'boolean';
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Check if schema is for date data
|
|
1174
|
+
*/
|
|
1175
|
+
isDate() {
|
|
1176
|
+
return this.dataType === 'date';
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Check if schema is for object data
|
|
1180
|
+
*/
|
|
1181
|
+
isObject() {
|
|
1182
|
+
return this.dataType === 'object';
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* Check if schema is for array data
|
|
1186
|
+
*/
|
|
1187
|
+
isArray() {
|
|
1188
|
+
return this.dataType === 'array';
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Check if schema is for blob data
|
|
1192
|
+
*/
|
|
1193
|
+
isBlob() {
|
|
1194
|
+
return this.dataType === 'blob';
|
|
1195
|
+
}
|
|
1196
|
+
//#endregion
|
|
1197
|
+
//#region ---- Interface Methods ----
|
|
1198
|
+
/**
|
|
1199
|
+
* Get widget type
|
|
1200
|
+
*/
|
|
1201
|
+
getWidgetType() {
|
|
1202
|
+
return this.interface.widget;
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Get widget options
|
|
1206
|
+
*/
|
|
1207
|
+
getWidgetOptions() {
|
|
1208
|
+
return this.interface.options;
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Check if interface is disabled
|
|
1212
|
+
*/
|
|
1213
|
+
isDisabled() {
|
|
1214
|
+
return this.interface.disabled ?? false;
|
|
1215
|
+
}
|
|
1216
|
+
//#endregion
|
|
1217
|
+
//#region ---- Feature Methods ----
|
|
1218
|
+
/**
|
|
1219
|
+
* Check if searchable
|
|
1220
|
+
*/
|
|
1221
|
+
isSearchable() {
|
|
1222
|
+
return this.features.searchable?.enabled ?? false;
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Check if full text searchable
|
|
1226
|
+
*/
|
|
1227
|
+
isFullTextSearchable() {
|
|
1228
|
+
return this.features.searchable?.fullText ?? false;
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Check if filterable
|
|
1232
|
+
*/
|
|
1233
|
+
isFilterable() {
|
|
1234
|
+
return this.features.filterable?.enabled ?? false;
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Check if inline filterable
|
|
1238
|
+
*/
|
|
1239
|
+
isInlineFilterable() {
|
|
1240
|
+
return this.features.filterable?.inline ?? false;
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Check if sortable
|
|
1244
|
+
*/
|
|
1245
|
+
isSortable() {
|
|
1246
|
+
return this.features.sortable?.enabled ?? false;
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Check if auditable
|
|
1250
|
+
*/
|
|
1251
|
+
isAuditable() {
|
|
1252
|
+
return this.features.auditable?.enabled ?? false;
|
|
1253
|
+
}
|
|
1254
|
+
//#endregion
|
|
1255
|
+
//#region ---- Validation Methods ----
|
|
1256
|
+
/**
|
|
1257
|
+
* Validate the schema structure
|
|
1258
|
+
*/
|
|
1259
|
+
async validate() {
|
|
1260
|
+
const errors = [];
|
|
1261
|
+
// Validate data type
|
|
1262
|
+
const validDataTypes = ['string', 'number', 'boolean', 'date', 'object', 'array', 'blob'];
|
|
1263
|
+
if (!validDataTypes.includes(this.dataType)) {
|
|
1264
|
+
errors.push(`Invalid data type: ${this.dataType}`);
|
|
1265
|
+
}
|
|
1266
|
+
// Validate interface
|
|
1267
|
+
if (!this.interface.widget) {
|
|
1268
|
+
errors.push('Widget type is required');
|
|
1269
|
+
}
|
|
1270
|
+
return errors;
|
|
1271
|
+
}
|
|
1272
|
+
//#endregion
|
|
1273
|
+
//#region ---- Utility Methods ----
|
|
1274
|
+
/**
|
|
1275
|
+
* Get schema capabilities
|
|
1276
|
+
*/
|
|
1277
|
+
getCapabilities() {
|
|
1278
|
+
return {
|
|
1279
|
+
searchable: this.isSearchable(),
|
|
1280
|
+
fullTextSearchable: this.isFullTextSearchable(),
|
|
1281
|
+
filterable: this.isFilterable(),
|
|
1282
|
+
inlineFilterable: this.isInlineFilterable(),
|
|
1283
|
+
sortable: this.isSortable(),
|
|
1284
|
+
auditable: this.isAuditable(),
|
|
1285
|
+
disabled: this.isDisabled()
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* Get schema summary
|
|
1290
|
+
*/
|
|
1291
|
+
getSummary() {
|
|
1292
|
+
const capabilities = this.getCapabilities();
|
|
1293
|
+
const capabilityCount = Object.values(capabilities).filter(Boolean).length;
|
|
1294
|
+
return {
|
|
1295
|
+
dataType: this.dataType,
|
|
1296
|
+
widget: this.getWidgetType(),
|
|
1297
|
+
hasOptions: !!this.interface.options,
|
|
1298
|
+
hasMetadata: !!this.metadata,
|
|
1299
|
+
hasValidations: !!this.validations && this.validations.length > 0,
|
|
1300
|
+
capabilityCount
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Convert back to interface definition
|
|
1305
|
+
*/
|
|
1306
|
+
toDefinition() {
|
|
1307
|
+
return {
|
|
1308
|
+
name: this.name,
|
|
1309
|
+
dataType: this.dataType,
|
|
1310
|
+
interface: this.interface,
|
|
1311
|
+
validations: this.validations,
|
|
1312
|
+
features: this.features,
|
|
1313
|
+
metadata: this.metadata
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
//#region ---- AXPModuleHelper ----
|
|
1319
|
+
/**
|
|
1320
|
+
* Helper utility class for working with module models
|
|
1321
|
+
*
|
|
1322
|
+
* @deprecated This helper class is deprecated and will be updated to work with the new domain registry system.
|
|
1323
|
+
* Use AXPDomainService for domain operations instead.
|
|
1324
|
+
*
|
|
1325
|
+
* TODO: Update this helper to work with the new simplified models and domain registry system.
|
|
1326
|
+
*/
|
|
1327
|
+
class AXPModuleHelper {
|
|
1328
|
+
//#endregion
|
|
1329
|
+
//#region ---- Deprecated Methods - To Be Updated ----
|
|
1330
|
+
/**
|
|
1331
|
+
* @deprecated Use AXPDomainService.findEntitiesByName() instead
|
|
1332
|
+
*/
|
|
1333
|
+
static findEntitiesByName(module, name) {
|
|
1334
|
+
console.warn('AXPModuleHelper.findEntitiesByName is deprecated. Use AXPDomainService instead.');
|
|
1335
|
+
return [];
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* @deprecated Use AXPDomainService with entity field queries instead
|
|
1339
|
+
*/
|
|
1340
|
+
static findFieldsByName(module, fieldName) {
|
|
1341
|
+
console.warn('AXPModuleHelper.findFieldsByName is deprecated. Use AXPDomainService instead.');
|
|
1342
|
+
return [];
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* @deprecated Use AXPDomainService with entity type queries instead
|
|
1346
|
+
*/
|
|
1347
|
+
static findEntitiesByType(module, type) {
|
|
1348
|
+
console.warn('AXPModuleHelper.findEntitiesByType is deprecated. Use AXPDomainService instead.');
|
|
1349
|
+
return [];
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* @deprecated Use AXPDomainService with field validation queries instead
|
|
1353
|
+
*/
|
|
1354
|
+
static findFieldsWithValidation(module, validationRule) {
|
|
1355
|
+
console.warn('AXPModuleHelper.findFieldsWithValidation is deprecated. Use AXPDomainService instead.');
|
|
1356
|
+
return [];
|
|
1357
|
+
}
|
|
1358
|
+
/**
|
|
1359
|
+
* @deprecated Use AXPDomainService with field queries instead
|
|
1360
|
+
*/
|
|
1361
|
+
static findFieldsByDataType(module, dataType) {
|
|
1362
|
+
console.warn('AXPModuleHelper.findFieldsByDataType is deprecated. Use AXPDomainService instead.');
|
|
1363
|
+
return [];
|
|
1364
|
+
}
|
|
1365
|
+
/**
|
|
1366
|
+
* @deprecated Use AXPDomainService with required field queries instead
|
|
1367
|
+
*/
|
|
1368
|
+
static findRequiredFields(module) {
|
|
1369
|
+
console.warn('AXPModuleHelper.findRequiredFields is deprecated. Use AXPDomainService instead.');
|
|
1370
|
+
return [];
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* @deprecated Use AXPDomainService with nullable field queries instead
|
|
1374
|
+
*/
|
|
1375
|
+
static findNullableFields(module) {
|
|
1376
|
+
console.warn('AXPModuleHelper.findNullableFields is deprecated. Use AXPDomainService instead.');
|
|
1377
|
+
return [];
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* @deprecated Use AXPDomainService with default value queries instead
|
|
1381
|
+
*/
|
|
1382
|
+
static findFieldsWithDefaultValues(module) {
|
|
1383
|
+
console.warn('AXPModuleHelper.findFieldsWithDefaultValues is deprecated. Use AXPDomainService instead.');
|
|
1384
|
+
return [];
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* @deprecated Use AXPDomainService with field schema queries instead
|
|
1388
|
+
*/
|
|
1389
|
+
static findFieldsBySchema(module, schemaName) {
|
|
1390
|
+
console.warn('AXPModuleHelper.findFieldsBySchema is deprecated. Use AXPDomainService instead.');
|
|
1391
|
+
return [];
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* @deprecated Use module.validate() method instead
|
|
1395
|
+
*/
|
|
1396
|
+
static validateModule(module) {
|
|
1397
|
+
return module.validate();
|
|
1398
|
+
}
|
|
1399
|
+
/**
|
|
1400
|
+
* @deprecated Use aggregate relation queries instead
|
|
1401
|
+
*/
|
|
1402
|
+
static findRelationsByType(module, relationType) {
|
|
1403
|
+
console.warn('AXPModuleHelper.findRelationsByType is deprecated. Use relation queries instead.');
|
|
1404
|
+
return [];
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* @deprecated Use AXPDomainService with entity dependency queries instead
|
|
1408
|
+
*/
|
|
1409
|
+
static findEntityDependencies(module, entityName) {
|
|
1410
|
+
console.warn('AXPModuleHelper.findEntityDependencies is deprecated. Use AXPDomainService instead.');
|
|
1411
|
+
return [];
|
|
1412
|
+
}
|
|
1413
|
+
/**
|
|
1414
|
+
* @deprecated Use module.getStatistics() method instead
|
|
1415
|
+
*/
|
|
1416
|
+
static getModuleStatistics(module) {
|
|
1417
|
+
return module.getStatistics();
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* @deprecated Use AXPDomainService with entity queries instead
|
|
1421
|
+
*/
|
|
1422
|
+
static getAllEntities(module) {
|
|
1423
|
+
console.warn('AXPModuleHelper.getAllEntities is deprecated. Use AXPDomainService instead.');
|
|
1424
|
+
return [];
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1427
|
+
* @deprecated Use AXPDomainService with field queries instead
|
|
1428
|
+
*/
|
|
1429
|
+
static getAllFields(module) {
|
|
1430
|
+
console.warn('AXPModuleHelper.getAllFields is deprecated. Use AXPDomainService instead.');
|
|
1431
|
+
return [];
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
//#region ---- Schema Middleware Context ----
|
|
1436
|
+
/**
|
|
1437
|
+
* Context class for schema middleware operations providing a fluent API for schema manipulation.
|
|
1438
|
+
*
|
|
1439
|
+
* This class serves as the interface between middleware functions and schema definitions,
|
|
1440
|
+
* offering methods to modify various aspects of a schema including:
|
|
1441
|
+
* - Widget configuration and options
|
|
1442
|
+
* - Validation rules
|
|
1443
|
+
* - Feature flags (searchable, filterable, sortable)
|
|
1444
|
+
* - Metadata and custom properties
|
|
1445
|
+
* - UI behavior (visibility, readonly state)
|
|
1446
|
+
*
|
|
1447
|
+
* All methods return `this` to enable fluent method chaining.
|
|
1448
|
+
*
|
|
1449
|
+
* @example
|
|
1450
|
+
* ```typescript
|
|
1451
|
+
* // Example middleware using the context
|
|
1452
|
+
* (context) => {
|
|
1453
|
+
* context
|
|
1454
|
+
* .addValidation({ rule: 'required' })
|
|
1455
|
+
* .searchable(true)
|
|
1456
|
+
* .withDefaultValue('default text')
|
|
1457
|
+
* .readonly(false);
|
|
1458
|
+
* }
|
|
1459
|
+
* ```
|
|
1460
|
+
*/
|
|
1461
|
+
class AXPSchemaMiddlewareContext {
|
|
1462
|
+
constructor(schema) {
|
|
1463
|
+
// Deep clone to avoid mutating the original schema
|
|
1464
|
+
this._schema = JSON.parse(JSON.stringify(schema));
|
|
1465
|
+
}
|
|
1466
|
+
//#region ---- Schema Access Properties ----
|
|
1467
|
+
/**
|
|
1468
|
+
* Get the current schema definition (readonly access)
|
|
1469
|
+
*/
|
|
1470
|
+
get schema() {
|
|
1471
|
+
return this._schema;
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* Get the schema name for conditional logic
|
|
1475
|
+
*/
|
|
1476
|
+
get name() {
|
|
1477
|
+
return this._schema.name;
|
|
1478
|
+
}
|
|
1479
|
+
//#endregion
|
|
1480
|
+
//#region ---- Default Value Management ----
|
|
1481
|
+
/**
|
|
1482
|
+
* Set a default value for the widget
|
|
1483
|
+
* @param defaultValue The default value to set
|
|
1484
|
+
*/
|
|
1485
|
+
withDefaultValue(defaultValue) {
|
|
1486
|
+
if (!this._schema.interface.options) {
|
|
1487
|
+
this._schema.interface.options = {};
|
|
1488
|
+
}
|
|
1489
|
+
this._schema.interface.options['defaultValue'] = defaultValue;
|
|
1490
|
+
return this;
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Remove the default value from the widget
|
|
1494
|
+
*/
|
|
1495
|
+
removeDefaultValue() {
|
|
1496
|
+
if (this._schema.interface.options) {
|
|
1497
|
+
delete this._schema.interface.options['defaultValue'];
|
|
1498
|
+
}
|
|
1499
|
+
return this;
|
|
1500
|
+
}
|
|
1501
|
+
//#endregion
|
|
1502
|
+
//#region ---- Validation Management ----
|
|
1503
|
+
/**
|
|
1504
|
+
* Add multiple validation rules to the schema
|
|
1505
|
+
* @param rules Array of validation rules to add
|
|
1506
|
+
*/
|
|
1507
|
+
withValidation(rules) {
|
|
1508
|
+
if (!this._schema.validations) {
|
|
1509
|
+
this._schema.validations = [];
|
|
1510
|
+
}
|
|
1511
|
+
this._schema.validations.push(...rules);
|
|
1512
|
+
return this;
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Add a single validation rule to the schema
|
|
1516
|
+
* @param rule The validation rule to add
|
|
1517
|
+
*/
|
|
1518
|
+
addValidation(rule) {
|
|
1519
|
+
if (!this._schema.validations) {
|
|
1520
|
+
this._schema.validations = [];
|
|
1521
|
+
}
|
|
1522
|
+
this._schema.validations.push(rule);
|
|
1523
|
+
return this;
|
|
1524
|
+
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Add a single validation rule (alias for addValidation for backward compatibility)
|
|
1527
|
+
* @param rule The validation rule to add
|
|
1528
|
+
*/
|
|
1529
|
+
withValidationRule(rule) {
|
|
1530
|
+
return this.addValidation(rule);
|
|
1531
|
+
}
|
|
1532
|
+
/**
|
|
1533
|
+
* Remove all validation rules from the schema
|
|
1534
|
+
*/
|
|
1535
|
+
clearValidations() {
|
|
1536
|
+
this._schema.validations = [];
|
|
1537
|
+
return this;
|
|
1538
|
+
}
|
|
1539
|
+
/**
|
|
1540
|
+
* Remove a specific validation rule by its rule name
|
|
1541
|
+
* @param ruleName The name of the rule to remove (e.g., 'required', 'email')
|
|
1542
|
+
*/
|
|
1543
|
+
removeValidation(ruleName) {
|
|
1544
|
+
if (this._schema.validations) {
|
|
1545
|
+
this._schema.validations = this._schema.validations.filter(v => v.rule !== ruleName);
|
|
1546
|
+
}
|
|
1547
|
+
return this;
|
|
1548
|
+
}
|
|
1549
|
+
//#endregion
|
|
1550
|
+
//#region ---- Widget Configuration ----
|
|
1551
|
+
/**
|
|
1552
|
+
* Set or merge widget options
|
|
1553
|
+
* @param options Object containing widget-specific options to merge
|
|
1554
|
+
*/
|
|
1555
|
+
withWidgetOptions(options) {
|
|
1556
|
+
this._schema.interface.options = {
|
|
1557
|
+
...this._schema.interface.options,
|
|
1558
|
+
...options
|
|
1559
|
+
};
|
|
1560
|
+
return this;
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Remove a specific widget option by key
|
|
1564
|
+
* @param optionKey The key of the option to remove
|
|
1565
|
+
*/
|
|
1566
|
+
removeWidgetOption(optionKey) {
|
|
1567
|
+
if (this._schema.interface.options) {
|
|
1568
|
+
delete this._schema.interface.options[optionKey];
|
|
1569
|
+
}
|
|
1570
|
+
return this;
|
|
1571
|
+
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Change the widget type for this schema
|
|
1574
|
+
* @param widgetType The new widget type identifier
|
|
1575
|
+
*/
|
|
1576
|
+
withWidgetType(widgetType) {
|
|
1577
|
+
this._schema.interface.widget = widgetType;
|
|
1578
|
+
return this;
|
|
1579
|
+
}
|
|
1580
|
+
//#endregion
|
|
1581
|
+
//#region ---- Feature Configuration ----
|
|
1582
|
+
/**
|
|
1583
|
+
* Configure general features using an object
|
|
1584
|
+
* @param features Object containing feature configurations to merge
|
|
1585
|
+
*/
|
|
1586
|
+
withFeatures(features) {
|
|
1587
|
+
this._schema.features = {
|
|
1588
|
+
...this._schema.features,
|
|
1589
|
+
...features
|
|
1590
|
+
};
|
|
1591
|
+
return this;
|
|
1592
|
+
}
|
|
1593
|
+
/**
|
|
1594
|
+
* Configure searchable feature with fine-grained control
|
|
1595
|
+
* @param enabled Whether searching is enabled
|
|
1596
|
+
* @param fullText Whether full-text search is enabled
|
|
1597
|
+
*/
|
|
1598
|
+
searchable(enabled = true, fullText = false) {
|
|
1599
|
+
if (!this._schema.features) {
|
|
1600
|
+
this._schema.features = {};
|
|
1601
|
+
}
|
|
1602
|
+
this._schema.features.searchable = { enabled, fullText };
|
|
1603
|
+
return this;
|
|
1604
|
+
}
|
|
1605
|
+
/**
|
|
1606
|
+
* Configure filterable feature with inline filter support
|
|
1607
|
+
* @param enabled Whether filtering is enabled
|
|
1608
|
+
* @param inline Whether inline filtering is supported
|
|
1609
|
+
*/
|
|
1610
|
+
filterable(enabled = true, inline = false) {
|
|
1611
|
+
if (!this._schema.features) {
|
|
1612
|
+
this._schema.features = {};
|
|
1613
|
+
}
|
|
1614
|
+
this._schema.features.filterable = { enabled, inline };
|
|
1615
|
+
return this;
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Configure sortable feature
|
|
1619
|
+
* @param enabled Whether sorting is enabled
|
|
1620
|
+
*/
|
|
1621
|
+
sortable(enabled = true) {
|
|
1622
|
+
if (!this._schema.features) {
|
|
1623
|
+
this._schema.features = {};
|
|
1624
|
+
}
|
|
1625
|
+
this._schema.features.sortable = { enabled };
|
|
1626
|
+
return this;
|
|
1627
|
+
}
|
|
1628
|
+
//#endregion
|
|
1629
|
+
//#region ---- Metadata Management ----
|
|
1630
|
+
/**
|
|
1631
|
+
* Add or merge metadata to the schema
|
|
1632
|
+
* @param metadata Object containing metadata to merge
|
|
1633
|
+
*/
|
|
1634
|
+
withMetadata(metadata) {
|
|
1635
|
+
this._schema.metadata = {
|
|
1636
|
+
...this._schema.metadata,
|
|
1637
|
+
...metadata
|
|
1638
|
+
};
|
|
1639
|
+
return this;
|
|
1640
|
+
}
|
|
1641
|
+
/**
|
|
1642
|
+
* Remove a specific metadata property by key
|
|
1643
|
+
* @param key The metadata key to remove
|
|
1644
|
+
*/
|
|
1645
|
+
removeMetadata(key) {
|
|
1646
|
+
if (this._schema.metadata) {
|
|
1647
|
+
delete this._schema.metadata[key];
|
|
1648
|
+
}
|
|
1649
|
+
return this;
|
|
1650
|
+
}
|
|
1651
|
+
//#endregion
|
|
1652
|
+
//#region ---- Schema Properties ----
|
|
1653
|
+
/**
|
|
1654
|
+
* Set the data type for this schema
|
|
1655
|
+
* @param dataType The new data type (string, number, boolean, etc.)
|
|
1656
|
+
*/
|
|
1657
|
+
withDataType(dataType) {
|
|
1658
|
+
this._schema.dataType = dataType;
|
|
1659
|
+
return this;
|
|
1660
|
+
}
|
|
1661
|
+
//#endregion
|
|
1662
|
+
//#region ---- UI State Management ----
|
|
1663
|
+
/**
|
|
1664
|
+
* Set the disabled state of the widget
|
|
1665
|
+
* @param isDisabled Whether the widget should be disabled
|
|
1666
|
+
*/
|
|
1667
|
+
disabled(isDisabled = true) {
|
|
1668
|
+
this._schema.interface.disabled = isDisabled;
|
|
1669
|
+
return this;
|
|
1670
|
+
}
|
|
1671
|
+
/**
|
|
1672
|
+
* Set the visibility of the widget
|
|
1673
|
+
* @param visible Whether the widget should be visible
|
|
1674
|
+
*/
|
|
1675
|
+
withVisibility(visible) {
|
|
1676
|
+
if (!this._schema.interface.options) {
|
|
1677
|
+
this._schema.interface.options = {};
|
|
1678
|
+
}
|
|
1679
|
+
this._schema.interface.options['visible'] = visible;
|
|
1680
|
+
return this;
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Set the readonly state of the widget
|
|
1684
|
+
* @param isReadonly Whether the widget should be readonly
|
|
1685
|
+
*/
|
|
1686
|
+
readonly(isReadonly = true) {
|
|
1687
|
+
if (!this._schema.interface.options) {
|
|
1688
|
+
this._schema.interface.options = {};
|
|
1689
|
+
}
|
|
1690
|
+
this._schema.interface.options['readonly'] = isReadonly;
|
|
1691
|
+
return this;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
//#endregion
|
|
1695
|
+
//#region ---- Builder-Based Middleware Types ----
|
|
1696
|
+
|
|
1697
|
+
//#endregion
|
|
1698
|
+
//#region ---- Schema Registry Service ----
|
|
1699
|
+
/**
|
|
1700
|
+
* Central registry for managing schema definitions, middleware, and dynamic loading.
|
|
1701
|
+
*
|
|
1702
|
+
* Provides:
|
|
1703
|
+
* - Schema registration and retrieval
|
|
1704
|
+
* - Middleware processing pipeline
|
|
1705
|
+
* - On-demand schema loading
|
|
1706
|
+
* - Caching and performance optimization
|
|
1707
|
+
*/
|
|
1708
|
+
class AXPSchemaRegistry {
|
|
1709
|
+
constructor() {
|
|
1710
|
+
this._schemas = new Map();
|
|
1711
|
+
this._globalMiddleware = [];
|
|
1712
|
+
this._modelCache = new Map();
|
|
1713
|
+
this._loaders = null;
|
|
1714
|
+
}
|
|
1715
|
+
//#region ---- Registration Methods ----
|
|
1716
|
+
/**
|
|
1717
|
+
* Register a schema definition with optional metadata and middleware
|
|
1718
|
+
*/
|
|
1719
|
+
register(definition, options = {}) {
|
|
1720
|
+
if (!definition.name || definition.name.trim() === '') {
|
|
1721
|
+
throw new Error('Schema name is required and cannot be empty');
|
|
1722
|
+
}
|
|
1723
|
+
const registered = {
|
|
1724
|
+
name: definition.name,
|
|
1725
|
+
definition,
|
|
1726
|
+
options,
|
|
1727
|
+
registeredAt: new Date()
|
|
1728
|
+
};
|
|
1729
|
+
this._schemas.set(definition.name, registered);
|
|
1730
|
+
this.invalidateCache(definition.name);
|
|
1731
|
+
console.log(`Schema '${definition.name}' registered successfully`);
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* Register multiple schemas at once for batch operations
|
|
1735
|
+
*/
|
|
1736
|
+
registerBatch(schemas) {
|
|
1737
|
+
for (const schema of schemas) {
|
|
1738
|
+
this.register(schema.definition, schema.options || {});
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
/**
|
|
1742
|
+
* Remove a schema from the registry
|
|
1743
|
+
*/
|
|
1744
|
+
unregister(name) {
|
|
1745
|
+
const removed = this._schemas.delete(name);
|
|
1746
|
+
if (removed) {
|
|
1747
|
+
this.invalidateCache(name);
|
|
1748
|
+
}
|
|
1749
|
+
return removed;
|
|
1750
|
+
}
|
|
1751
|
+
/**
|
|
1752
|
+
* Check if a schema is currently registered
|
|
1753
|
+
*/
|
|
1754
|
+
isRegistered(name) {
|
|
1755
|
+
return this._schemas.has(name);
|
|
1756
|
+
}
|
|
1757
|
+
//#endregion
|
|
1758
|
+
//#region ---- Resolution Methods ----
|
|
1759
|
+
/**
|
|
1760
|
+
* Resolve a schema by name with full middleware processing and caching.
|
|
1761
|
+
* Supports on-demand loading if schema is not registered.
|
|
1762
|
+
*/
|
|
1763
|
+
async resolve(name) {
|
|
1764
|
+
// Check cache first for performance
|
|
1765
|
+
if (this._modelCache.has(name)) {
|
|
1766
|
+
return this._modelCache.get(name);
|
|
1767
|
+
}
|
|
1768
|
+
// Try to get from registered schemas first
|
|
1769
|
+
let registered = this._schemas.get(name);
|
|
1770
|
+
// If not found, attempt on-demand loading
|
|
1771
|
+
if (!registered) {
|
|
1772
|
+
const definition = await this.loadFromLoaders(name);
|
|
1773
|
+
if (definition) {
|
|
1774
|
+
registered = {
|
|
1775
|
+
name,
|
|
1776
|
+
definition,
|
|
1777
|
+
options: {},
|
|
1778
|
+
registeredAt: new Date()
|
|
1779
|
+
};
|
|
1780
|
+
this._schemas.set(name, registered);
|
|
1781
|
+
}
|
|
1782
|
+
else {
|
|
1783
|
+
throw new Error(`Schema '${name}' is not registered and no loader can provide it`);
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
// Clone and process through middleware pipeline
|
|
1787
|
+
const schema = this.cloneDefinition(registered.definition);
|
|
1788
|
+
const context = new AXPSchemaMiddlewareContext(schema);
|
|
1789
|
+
// Apply global middleware first
|
|
1790
|
+
for (const middleware of this._globalMiddleware) {
|
|
1791
|
+
await middleware(context);
|
|
1792
|
+
}
|
|
1793
|
+
// Apply schema-specific middleware
|
|
1794
|
+
if (registered.options.middleware) {
|
|
1795
|
+
for (const middleware of registered.options.middleware) {
|
|
1796
|
+
await middleware(context);
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
// Create final model and cache it
|
|
1800
|
+
const model = new AXPSchemaModel(context.schema);
|
|
1801
|
+
this._modelCache.set(name, model);
|
|
1802
|
+
return model;
|
|
1803
|
+
}
|
|
1804
|
+
/**
|
|
1805
|
+
* Synchronous resolution without middleware processing.
|
|
1806
|
+
* Use only when middleware is not needed for performance.
|
|
1807
|
+
*/
|
|
1808
|
+
resolveSync(name) {
|
|
1809
|
+
const registered = this._schemas.get(name);
|
|
1810
|
+
if (!registered) {
|
|
1811
|
+
throw new Error(`Schema '${name}' is not registered`);
|
|
1812
|
+
}
|
|
1813
|
+
return new AXPSchemaModel(this.cloneDefinition(registered.definition));
|
|
1814
|
+
}
|
|
1815
|
+
//#endregion
|
|
1816
|
+
//#region ---- Middleware Management ----
|
|
1817
|
+
/**
|
|
1818
|
+
* Add global middleware that applies to all schema resolutions
|
|
1819
|
+
*/
|
|
1820
|
+
addGlobalMiddleware(middleware) {
|
|
1821
|
+
if (typeof middleware !== 'function') {
|
|
1822
|
+
throw new Error('Middleware must be a function');
|
|
1823
|
+
}
|
|
1824
|
+
this._globalMiddleware.push(middleware);
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Remove specific global middleware
|
|
1828
|
+
*/
|
|
1829
|
+
removeGlobalMiddleware(middleware) {
|
|
1830
|
+
const index = this._globalMiddleware.indexOf(middleware);
|
|
1831
|
+
if (index > -1) {
|
|
1832
|
+
this._globalMiddleware.splice(index, 1);
|
|
1833
|
+
return true;
|
|
1834
|
+
}
|
|
1835
|
+
return false;
|
|
1836
|
+
}
|
|
1837
|
+
/**
|
|
1838
|
+
* Clear all global middleware
|
|
1839
|
+
*/
|
|
1840
|
+
clearGlobalMiddleware() {
|
|
1841
|
+
this._globalMiddleware.length = 0;
|
|
1842
|
+
}
|
|
1843
|
+
//#endregion
|
|
1844
|
+
//#region ---- Loader Management ----
|
|
1845
|
+
/**
|
|
1846
|
+
* Add a schema loader for on-demand loading
|
|
1847
|
+
*/
|
|
1848
|
+
addLoader(loader) {
|
|
1849
|
+
if (this._loaders === null) {
|
|
1850
|
+
this._loaders = [];
|
|
1851
|
+
}
|
|
1852
|
+
this._loaders.push(new loader());
|
|
1853
|
+
}
|
|
1854
|
+
//#endregion
|
|
1855
|
+
//#region ---- Query Methods ----
|
|
1856
|
+
/**
|
|
1857
|
+
* Get all registered schema names
|
|
1858
|
+
*/
|
|
1859
|
+
getRegisteredNames() {
|
|
1860
|
+
return Array.from(this._schemas.keys());
|
|
1861
|
+
}
|
|
1862
|
+
/**
|
|
1863
|
+
* Get detailed registration information for a schema
|
|
1864
|
+
*/
|
|
1865
|
+
getSchemaInfo(name) {
|
|
1866
|
+
return this._schemas.get(name);
|
|
1867
|
+
}
|
|
1868
|
+
/**
|
|
1869
|
+
* Get all registered schemas with their metadata
|
|
1870
|
+
*/
|
|
1871
|
+
getAllSchemas() {
|
|
1872
|
+
return Array.from(this._schemas.values());
|
|
1873
|
+
}
|
|
1874
|
+
/**
|
|
1875
|
+
* Find schemas by tag for categorization
|
|
1876
|
+
*/
|
|
1877
|
+
findByTag(tag) {
|
|
1878
|
+
return Array.from(this._schemas.values()).filter(schema => schema.options.tags?.includes(tag));
|
|
1879
|
+
}
|
|
1880
|
+
/**
|
|
1881
|
+
* Find schemas by widget type for widget-specific operations
|
|
1882
|
+
*/
|
|
1883
|
+
findByWidget(widgetType) {
|
|
1884
|
+
return Array.from(this._schemas.values()).filter(schema => schema.definition.interface.widget === widgetType);
|
|
1885
|
+
}
|
|
1886
|
+
/**
|
|
1887
|
+
* Get comprehensive registry statistics
|
|
1888
|
+
*/
|
|
1889
|
+
getStatistics() {
|
|
1890
|
+
const schemasByWidget = {};
|
|
1891
|
+
const schemasByTag = {};
|
|
1892
|
+
for (const schema of this._schemas.values()) {
|
|
1893
|
+
const widget = schema.definition.interface.widget;
|
|
1894
|
+
schemasByWidget[widget] = (schemasByWidget[widget] || 0) + 1;
|
|
1895
|
+
if (schema.options.tags) {
|
|
1896
|
+
for (const tag of schema.options.tags) {
|
|
1897
|
+
schemasByTag[tag] = (schemasByTag[tag] || 0) + 1;
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
return {
|
|
1902
|
+
schemaCount: this._schemas.size,
|
|
1903
|
+
globalMiddlewareCount: this._globalMiddleware.length,
|
|
1904
|
+
schemasByWidget,
|
|
1905
|
+
schemasByTag
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
//#endregion
|
|
1909
|
+
//#region ---- Cache Management ----
|
|
1910
|
+
/**
|
|
1911
|
+
* Clear all cached models (useful when schemas need to be reloaded)
|
|
1912
|
+
*/
|
|
1913
|
+
clearCache() {
|
|
1914
|
+
this._modelCache.clear();
|
|
1915
|
+
}
|
|
1916
|
+
/**
|
|
1917
|
+
* Invalidate cache for a specific schema
|
|
1918
|
+
*/
|
|
1919
|
+
invalidateCache(name) {
|
|
1920
|
+
this._modelCache.delete(name);
|
|
1921
|
+
}
|
|
1922
|
+
/**
|
|
1923
|
+
* Clear all registered schemas, middleware, and cached models
|
|
1924
|
+
*/
|
|
1925
|
+
clear() {
|
|
1926
|
+
this._schemas.clear();
|
|
1927
|
+
this._globalMiddleware.length = 0;
|
|
1928
|
+
this._modelCache.clear();
|
|
1929
|
+
this._loaders = null;
|
|
1930
|
+
}
|
|
1931
|
+
//#endregion
|
|
1932
|
+
//#region ---- Private Helper Methods ----
|
|
1933
|
+
/**
|
|
1934
|
+
* Attempt to load schema from registered loaders
|
|
1935
|
+
*/
|
|
1936
|
+
async loadFromLoaders(name) {
|
|
1937
|
+
const loaders = this.getLoaders();
|
|
1938
|
+
for (const loader of loaders) {
|
|
1939
|
+
if (loader.canLoad(name)) {
|
|
1940
|
+
try {
|
|
1941
|
+
const definition = await loader.load(name);
|
|
1942
|
+
if (definition) {
|
|
1943
|
+
console.log(`Schema '${name}' loaded from ${loader.constructor.name}`);
|
|
1944
|
+
return definition;
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
catch (error) {
|
|
1948
|
+
console.warn(`Loader ${loader.constructor.name} failed to load '${name}':`, error);
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
return null;
|
|
1953
|
+
}
|
|
1954
|
+
/**
|
|
1955
|
+
* Get loaders sorted by priority (lazy initialization)
|
|
1956
|
+
*/
|
|
1957
|
+
getLoaders() {
|
|
1958
|
+
if (this._loaders === null) {
|
|
1959
|
+
try {
|
|
1960
|
+
this._loaders = [];
|
|
1961
|
+
// Sort by priority (higher priority first)
|
|
1962
|
+
this._loaders.sort((a, b) => {
|
|
1963
|
+
const priorityA = a.priority ?? 0;
|
|
1964
|
+
const priorityB = b.priority ?? 0;
|
|
1965
|
+
return priorityB - priorityA;
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1968
|
+
catch (error) {
|
|
1969
|
+
console.warn('Failed to initialize schema loaders:', error);
|
|
1970
|
+
this._loaders = [];
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
return this._loaders;
|
|
1974
|
+
}
|
|
1975
|
+
/**
|
|
1976
|
+
* Create a deep clone of a schema definition to prevent mutations
|
|
1977
|
+
*/
|
|
1978
|
+
cloneDefinition(definition) {
|
|
1979
|
+
return JSON.parse(JSON.stringify(definition));
|
|
1980
|
+
}
|
|
1981
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AXPSchemaRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1982
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AXPSchemaRegistry, providedIn: 'root' }); }
|
|
1983
|
+
}
|
|
1984
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AXPSchemaRegistry, decorators: [{
|
|
1985
|
+
type: Injectable,
|
|
1986
|
+
args: [{ providedIn: 'root' }]
|
|
1987
|
+
}], ctorParameters: () => [] });
|
|
1988
|
+
|
|
1989
|
+
//#region ---- Schema Service ----
|
|
1990
|
+
/**
|
|
1991
|
+
* High-level facade service for schema operations.
|
|
1992
|
+
*
|
|
1993
|
+
* Provides a simplified interface to the schema registry with additional
|
|
1994
|
+
* business logic and convenience methods for common operations.
|
|
1995
|
+
*/
|
|
1996
|
+
class AXPSchemaService {
|
|
1997
|
+
constructor() {
|
|
1998
|
+
this.registry = inject(AXPSchemaRegistry);
|
|
1999
|
+
}
|
|
2000
|
+
//#region ---- Core Schema Management ----
|
|
2001
|
+
/**
|
|
2002
|
+
* Register a schema with optional configuration
|
|
2003
|
+
*/
|
|
2004
|
+
register(definition, options) {
|
|
2005
|
+
this.registry.register(definition, options);
|
|
2006
|
+
}
|
|
2007
|
+
/**
|
|
2008
|
+
* Register multiple schemas at once
|
|
2009
|
+
*/
|
|
2010
|
+
registerBatch(schemas) {
|
|
2011
|
+
this.registry.registerBatch(schemas);
|
|
2012
|
+
}
|
|
2013
|
+
/**
|
|
2014
|
+
* Remove a schema from the registry
|
|
2015
|
+
*/
|
|
2016
|
+
unregister(name) {
|
|
2017
|
+
return this.registry.unregister(name);
|
|
2018
|
+
}
|
|
2019
|
+
/**
|
|
2020
|
+
* Check if a schema is registered
|
|
2021
|
+
*/
|
|
2022
|
+
isRegistered(name) {
|
|
2023
|
+
return this.registry.isRegistered(name);
|
|
2024
|
+
}
|
|
2025
|
+
/**
|
|
2026
|
+
* Get all registered schema names
|
|
2027
|
+
*/
|
|
2028
|
+
getRegisteredNames() {
|
|
2029
|
+
return this.registry.getRegisteredNames();
|
|
2030
|
+
}
|
|
2031
|
+
/**
|
|
2032
|
+
* Clear all schemas and reset the registry
|
|
2033
|
+
*/
|
|
2034
|
+
clear() {
|
|
2035
|
+
this.registry.clear();
|
|
2036
|
+
}
|
|
2037
|
+
//#endregion
|
|
2038
|
+
//#region ---- Schema Resolution ----
|
|
2039
|
+
/**
|
|
2040
|
+
* Resolve a schema with full middleware processing
|
|
2041
|
+
*/
|
|
2042
|
+
async resolve(name) {
|
|
2043
|
+
return this.registry.resolve(name);
|
|
2044
|
+
}
|
|
2045
|
+
/**
|
|
2046
|
+
* Resolve schema synchronously without middleware (for performance)
|
|
2047
|
+
*/
|
|
2048
|
+
resolveSync(name) {
|
|
2049
|
+
return this.registry.resolveSync(name);
|
|
2050
|
+
}
|
|
2051
|
+
/**
|
|
2052
|
+
* Resolve multiple schemas for form building and batch operations
|
|
2053
|
+
*/
|
|
2054
|
+
async resolveMultiple(fieldConfigs) {
|
|
2055
|
+
const resolvedFields = await Promise.all(fieldConfigs.map(async (config) => ({
|
|
2056
|
+
name: config.name,
|
|
2057
|
+
schema: await this.resolve(config.schemaName)
|
|
2058
|
+
})));
|
|
2059
|
+
return resolvedFields;
|
|
2060
|
+
}
|
|
2061
|
+
//#endregion
|
|
2062
|
+
//#region ---- Middleware Management ----
|
|
2063
|
+
/**
|
|
2064
|
+
* Add global middleware that applies to all schema resolutions
|
|
2065
|
+
*/
|
|
2066
|
+
addGlobalMiddleware(middleware) {
|
|
2067
|
+
this.registry.addGlobalMiddleware(middleware);
|
|
2068
|
+
}
|
|
2069
|
+
/**
|
|
2070
|
+
* Remove specific global middleware
|
|
2071
|
+
*/
|
|
2072
|
+
removeGlobalMiddleware(middleware) {
|
|
2073
|
+
return this.registry.removeGlobalMiddleware(middleware);
|
|
2074
|
+
}
|
|
2075
|
+
/**
|
|
2076
|
+
* Clear all global middleware
|
|
2077
|
+
*/
|
|
2078
|
+
clearGlobalMiddleware() {
|
|
2079
|
+
this.registry.clearGlobalMiddleware();
|
|
2080
|
+
}
|
|
2081
|
+
//#endregion
|
|
2082
|
+
//#region ---- Query and Discovery ----
|
|
2083
|
+
/**
|
|
2084
|
+
* Get detailed information about a registered schema
|
|
2085
|
+
*/
|
|
2086
|
+
getSchemaInfo(name) {
|
|
2087
|
+
return this.registry.getSchemaInfo(name);
|
|
2088
|
+
}
|
|
2089
|
+
/**
|
|
2090
|
+
* Get all registered schemas with their metadata
|
|
2091
|
+
*/
|
|
2092
|
+
getAllSchemas() {
|
|
2093
|
+
return this.registry.getAllSchemas();
|
|
2094
|
+
}
|
|
2095
|
+
/**
|
|
2096
|
+
* Find schemas by tag for categorization and grouping
|
|
2097
|
+
*/
|
|
2098
|
+
findByTag(tag) {
|
|
2099
|
+
return this.registry.findByTag(tag);
|
|
2100
|
+
}
|
|
2101
|
+
/**
|
|
2102
|
+
* Find schemas by widget type for widget-specific operations
|
|
2103
|
+
*/
|
|
2104
|
+
findByWidget(widgetType) {
|
|
2105
|
+
return this.registry.findByWidget(widgetType);
|
|
2106
|
+
}
|
|
2107
|
+
/**
|
|
2108
|
+
* Get comprehensive registry statistics and analytics
|
|
2109
|
+
*/
|
|
2110
|
+
getStatistics() {
|
|
2111
|
+
const stats = this.registry.getStatistics();
|
|
2112
|
+
// Enhance with top-used analytics
|
|
2113
|
+
const mostUsedWidgets = Object.entries(stats.schemasByWidget)
|
|
2114
|
+
.sort(([, a], [, b]) => b - a)
|
|
2115
|
+
.slice(0, 5)
|
|
2116
|
+
.map(([widget]) => widget);
|
|
2117
|
+
const mostUsedTags = Object.entries(stats.schemasByTag)
|
|
2118
|
+
.sort(([, a], [, b]) => b - a)
|
|
2119
|
+
.slice(0, 5)
|
|
2120
|
+
.map(([tag]) => tag);
|
|
2121
|
+
return {
|
|
2122
|
+
...stats,
|
|
2123
|
+
mostUsedWidgets,
|
|
2124
|
+
mostUsedTags
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
//#endregion
|
|
2128
|
+
//#region ---- Validation and Analysis ----
|
|
2129
|
+
/**
|
|
2130
|
+
* Validate that all referenced schemas exist in the registry
|
|
2131
|
+
*/
|
|
2132
|
+
validateSchemaReferences(schemaNames) {
|
|
2133
|
+
const missingSchemas = [];
|
|
2134
|
+
for (const schemaName of schemaNames) {
|
|
2135
|
+
if (!this.isRegistered(schemaName)) {
|
|
2136
|
+
missingSchemas.push(schemaName);
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
return {
|
|
2140
|
+
valid: missingSchemas.length === 0,
|
|
2141
|
+
missingSchemas
|
|
2142
|
+
};
|
|
2143
|
+
}
|
|
2144
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AXPSchemaService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2145
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AXPSchemaService, providedIn: 'root' }); }
|
|
2146
|
+
}
|
|
2147
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AXPSchemaService, decorators: [{
|
|
2148
|
+
type: Injectable,
|
|
2149
|
+
args: [{ providedIn: 'root' }]
|
|
2150
|
+
}] });
|
|
2151
|
+
|
|
2152
|
+
//#endregion
|
|
2153
|
+
|
|
2154
|
+
//#endregion
|
|
2155
|
+
//#region ---- Provider Functions ----
|
|
2156
|
+
/**
|
|
2157
|
+
* Provide schema loaders for on-demand schema loading.
|
|
2158
|
+
*
|
|
2159
|
+
* Schema loaders enable the registry to load schemas that are not registered
|
|
2160
|
+
* at build time. This is useful for:
|
|
2161
|
+
* - Loading schemas from external APIs
|
|
2162
|
+
* - Dynamic schema generation
|
|
2163
|
+
* - Lazy loading of large schema sets
|
|
2164
|
+
* - Development-time schema hot-reloading
|
|
2165
|
+
*
|
|
2166
|
+
* @param loaders Array of loader classes or loader configurations
|
|
2167
|
+
* @returns Environment providers for dependency injection
|
|
2168
|
+
*
|
|
2169
|
+
* @example
|
|
2170
|
+
* ```typescript
|
|
2171
|
+
* // Simple loader registration
|
|
2172
|
+
* provideSchemaLoaders([
|
|
2173
|
+
* HttpSchemaLoader,
|
|
2174
|
+
* FileSystemSchemaLoader
|
|
2175
|
+
* ])
|
|
2176
|
+
*
|
|
2177
|
+
* // With priority configuration
|
|
2178
|
+
* provideSchemaLoaders([
|
|
2179
|
+
* { loader: HttpSchemaLoader, priority: 10 },
|
|
2180
|
+
* { loader: FileSystemSchemaLoader, priority: 5 }
|
|
2181
|
+
* ])
|
|
2182
|
+
* ```
|
|
2183
|
+
*/
|
|
2184
|
+
function provideSchemaLoaders(loaders) {
|
|
2185
|
+
return makeEnvironmentProviders([
|
|
2186
|
+
{
|
|
2187
|
+
provide: AXP_SCHEMA_LOADER_SETUP,
|
|
2188
|
+
useFactory: async () => {
|
|
2189
|
+
const registry = inject(AXPSchemaRegistry);
|
|
2190
|
+
// Register each loader with the registry
|
|
2191
|
+
for (const loader of loaders) {
|
|
2192
|
+
const loaderClass = typeof loader === 'function' ? loader : loader.loader;
|
|
2193
|
+
registry.addLoader(loaderClass);
|
|
2194
|
+
}
|
|
2195
|
+
return true;
|
|
2196
|
+
},
|
|
2197
|
+
multi: true
|
|
2198
|
+
}
|
|
2199
|
+
]);
|
|
2200
|
+
}
|
|
2201
|
+
//#endregion
|
|
2202
|
+
|
|
2203
|
+
//#endregion
|
|
2204
|
+
//#region ---- Provider Functions ----
|
|
2205
|
+
/**
|
|
2206
|
+
* Provide schema setups for registration during application bootstrap.
|
|
2207
|
+
*
|
|
2208
|
+
* This is the primary way to register schemas that are known at build time.
|
|
2209
|
+
* Schemas registered this way are immediately available in the registry.
|
|
2210
|
+
*
|
|
2211
|
+
* @param schemas Array of schema entries to register
|
|
2212
|
+
* @returns Environment providers for dependency injection
|
|
2213
|
+
*
|
|
2214
|
+
* @example
|
|
2215
|
+
* ```typescript
|
|
2216
|
+
* provideSchemaSetups([
|
|
2217
|
+
* {
|
|
2218
|
+
* definition: emailSchema,
|
|
2219
|
+
* options: {
|
|
2220
|
+
* tags: ['user', 'contact'],
|
|
2221
|
+
* description: 'Email field schema'
|
|
2222
|
+
* }
|
|
2223
|
+
* },
|
|
2224
|
+
* {
|
|
2225
|
+
* definition: phoneSchema,
|
|
2226
|
+
* options: { tags: ['contact'] }
|
|
2227
|
+
* }
|
|
2228
|
+
* ])
|
|
2229
|
+
* ```
|
|
2230
|
+
*/
|
|
2231
|
+
function provideSchemaSetups(schemas) {
|
|
2232
|
+
return makeEnvironmentProviders([
|
|
2233
|
+
{
|
|
2234
|
+
provide: AXP_SCHEMA_SETUP,
|
|
2235
|
+
useFactory: () => {
|
|
2236
|
+
const registry = inject(AXPSchemaRegistry);
|
|
2237
|
+
// Register all schemas with their options
|
|
2238
|
+
for (const schema of schemas) {
|
|
2239
|
+
registry.register(schema.definition, schema.options);
|
|
2240
|
+
}
|
|
2241
|
+
return true;
|
|
2242
|
+
},
|
|
2243
|
+
multi: true
|
|
2244
|
+
}
|
|
2245
|
+
]);
|
|
2246
|
+
}
|
|
2247
|
+
/**
|
|
2248
|
+
* Convenience function to provide a single schema setup.
|
|
2249
|
+
*
|
|
2250
|
+
* Useful when you need to register just one schema or want to keep
|
|
2251
|
+
* schema registrations separate for organizational purposes.
|
|
2252
|
+
*
|
|
2253
|
+
* @param definition The schema definition to register
|
|
2254
|
+
* @param options Optional registration options and metadata
|
|
2255
|
+
* @returns Environment providers for dependency injection
|
|
2256
|
+
*
|
|
2257
|
+
* @example
|
|
2258
|
+
* ```typescript
|
|
2259
|
+
* provideSchema(emailSchema, {
|
|
2260
|
+
* tags: ['user', 'contact'],
|
|
2261
|
+
* description: 'User email field'
|
|
2262
|
+
* })
|
|
2263
|
+
* ```
|
|
2264
|
+
*/
|
|
2265
|
+
function provideSchema(definition, options) {
|
|
2266
|
+
return provideSchemaSetups([{ definition, options }]);
|
|
2267
|
+
}
|
|
2268
|
+
/**
|
|
2269
|
+
* Provide schema setups from a factory function for dynamic registration.
|
|
2270
|
+
*
|
|
2271
|
+
* Useful when schemas need to be generated at runtime, loaded from external
|
|
2272
|
+
* sources, or depend on configuration that's not available at build time.
|
|
2273
|
+
*
|
|
2274
|
+
* @param schemaFactory Factory function that returns schema entries
|
|
2275
|
+
* @returns Environment providers for dependency injection
|
|
2276
|
+
*
|
|
2277
|
+
* @example
|
|
2278
|
+
* ```typescript
|
|
2279
|
+
* provideSchemasFromFactory(async () => {
|
|
2280
|
+
* const config = await loadConfiguration();
|
|
2281
|
+
* return config.schemas.map(schema => ({
|
|
2282
|
+
* definition: schema,
|
|
2283
|
+
* options: { tags: ['dynamic'] }
|
|
2284
|
+
* }));
|
|
2285
|
+
* })
|
|
2286
|
+
* ```
|
|
2287
|
+
*/
|
|
2288
|
+
function provideSchemasFromFactory(schemaFactory) {
|
|
2289
|
+
return makeEnvironmentProviders([
|
|
2290
|
+
{
|
|
2291
|
+
provide: AXP_SCHEMA_SETUP,
|
|
2292
|
+
useFactory: async () => {
|
|
2293
|
+
const registry = inject(AXPSchemaRegistry);
|
|
2294
|
+
const schemas = await schemaFactory();
|
|
2295
|
+
// Register all factory-generated schemas
|
|
2296
|
+
for (const schema of schemas) {
|
|
2297
|
+
registry.register(schema.definition, schema.options);
|
|
2298
|
+
}
|
|
2299
|
+
return true;
|
|
2300
|
+
},
|
|
2301
|
+
multi: true
|
|
2302
|
+
}
|
|
2303
|
+
]);
|
|
2304
|
+
}
|
|
2305
|
+
//#endregion
|
|
2306
|
+
|
|
2307
|
+
//#endregion
|
|
2308
|
+
//#region ---- Provider Functions ----
|
|
2309
|
+
/**
|
|
2310
|
+
* Provide schema middleware for registration in the application.
|
|
2311
|
+
*
|
|
2312
|
+
* Supports both global middleware (applies to all schemas) and targeted middleware
|
|
2313
|
+
* (applies only to schemas matching specific patterns).
|
|
2314
|
+
*
|
|
2315
|
+
* @param middleware Array of middleware entries to register
|
|
2316
|
+
* @returns Environment providers for dependency injection
|
|
2317
|
+
*
|
|
2318
|
+
* @example
|
|
2319
|
+
* ```typescript
|
|
2320
|
+
* // Global middleware
|
|
2321
|
+
* provideSchemaMiddleware([
|
|
2322
|
+
* (context) => {
|
|
2323
|
+
* if (context.schema.dataType === 'string') {
|
|
2324
|
+
* context.searchable(true);
|
|
2325
|
+
* }
|
|
2326
|
+
* }
|
|
2327
|
+
* ])
|
|
2328
|
+
*
|
|
2329
|
+
* // Targeted middleware
|
|
2330
|
+
* provideSchemaMiddleware([
|
|
2331
|
+
* {
|
|
2332
|
+
* target: /^user_/,
|
|
2333
|
+
* middleware: (context) => context.withMetadata({ category: 'user' })
|
|
2334
|
+
* }
|
|
2335
|
+
* ])
|
|
2336
|
+
* ```
|
|
2337
|
+
*/
|
|
2338
|
+
function provideSchemaMiddleware(middleware) {
|
|
2339
|
+
const global = [];
|
|
2340
|
+
const targeted = [];
|
|
2341
|
+
// Separate global and targeted middleware for different processing
|
|
2342
|
+
for (const entry of middleware) {
|
|
2343
|
+
if (typeof entry === 'function') {
|
|
2344
|
+
global.push(entry);
|
|
2345
|
+
}
|
|
2346
|
+
else {
|
|
2347
|
+
targeted.push(entry);
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
return makeEnvironmentProviders([
|
|
2351
|
+
// Setup global middleware that applies to all schemas
|
|
2352
|
+
...(global.length > 0 ? [{
|
|
2353
|
+
provide: AXP_SCHEMA_MIDDLEWARE_SETUP,
|
|
2354
|
+
useFactory: () => {
|
|
2355
|
+
const registry = inject(AXPSchemaRegistry);
|
|
2356
|
+
for (const mw of global) {
|
|
2357
|
+
registry.addGlobalMiddleware(mw);
|
|
2358
|
+
}
|
|
2359
|
+
return true;
|
|
2360
|
+
},
|
|
2361
|
+
multi: true
|
|
2362
|
+
}] : []),
|
|
2363
|
+
// Register targeted middleware entries for future extension support
|
|
2364
|
+
...targeted.map(entry => ({
|
|
2365
|
+
provide: AXP_SCHEMA_EXTENSION,
|
|
2366
|
+
useValue: entry,
|
|
2367
|
+
multi: true
|
|
2368
|
+
}))
|
|
2369
|
+
]);
|
|
2370
|
+
}
|
|
2371
|
+
//#endregion
|
|
2372
|
+
|
|
2373
|
+
//#region ---- Core Schema Registry ----
|
|
2374
|
+
// Core registry and service classes
|
|
2375
|
+
//#endregion
|
|
2376
|
+
|
|
2377
|
+
//#region ---- Domain Middleware Context ----
|
|
2378
|
+
/**
|
|
2379
|
+
* Context class for domain middleware operations providing a fluent API for domain definition manipulation.
|
|
2380
|
+
*
|
|
2381
|
+
* This class serves as the interface between middleware functions and domain definitions,
|
|
2382
|
+
* offering methods to modify various aspects of a domain definition including:
|
|
2383
|
+
* - Metadata and custom properties
|
|
2384
|
+
* - Validation rules
|
|
2385
|
+
* - Feature flags and configuration
|
|
2386
|
+
* - Transformations and augmentations
|
|
2387
|
+
*
|
|
2388
|
+
* All methods return `this` to enable fluent method chaining.
|
|
2389
|
+
*/
|
|
2390
|
+
class AXPDomainMiddlewareContext {
|
|
2391
|
+
constructor(definition, type) {
|
|
2392
|
+
// Deep clone to avoid mutating the original definition
|
|
2393
|
+
this._definition = JSON.parse(JSON.stringify(definition));
|
|
2394
|
+
this._type = type;
|
|
2395
|
+
}
|
|
2396
|
+
/**
|
|
2397
|
+
* Get the current definition (read-only access)
|
|
2398
|
+
*/
|
|
2399
|
+
get definition() {
|
|
2400
|
+
return this._definition;
|
|
2401
|
+
}
|
|
2402
|
+
/**
|
|
2403
|
+
* Get the type of domain object being processed
|
|
2404
|
+
*/
|
|
2405
|
+
get type() {
|
|
2406
|
+
return this._type;
|
|
2407
|
+
}
|
|
2408
|
+
/**
|
|
2409
|
+
* Set metadata for the domain definition
|
|
2410
|
+
*/
|
|
2411
|
+
setMetadata(key, value) {
|
|
2412
|
+
const def = this._definition;
|
|
2413
|
+
if (!def.metadata) {
|
|
2414
|
+
def.metadata = {};
|
|
2415
|
+
}
|
|
2416
|
+
def.metadata[key] = value;
|
|
2417
|
+
return this;
|
|
2418
|
+
}
|
|
2419
|
+
/**
|
|
2420
|
+
* Get metadata value
|
|
2421
|
+
*/
|
|
2422
|
+
getMetadata(key) {
|
|
2423
|
+
const def = this._definition;
|
|
2424
|
+
return def.metadata?.[key];
|
|
2425
|
+
}
|
|
2426
|
+
/**
|
|
2427
|
+
* Add validation rule to the definition (for entities and aggregates)
|
|
2428
|
+
*/
|
|
2429
|
+
addValidation(rule) {
|
|
2430
|
+
if ('validations' in this._definition) {
|
|
2431
|
+
this._definition.validations = this._definition.validations || [];
|
|
2432
|
+
this._definition.validations.push(rule);
|
|
2433
|
+
}
|
|
2434
|
+
return this;
|
|
2435
|
+
}
|
|
2436
|
+
/**
|
|
2437
|
+
* Transform fields (for entities)
|
|
2438
|
+
*/
|
|
2439
|
+
transformFields(transformer) {
|
|
2440
|
+
if ('fields' in this._definition && this._definition.fields) {
|
|
2441
|
+
this._definition.fields = this._definition.fields.map(transformer);
|
|
2442
|
+
}
|
|
2443
|
+
return this;
|
|
2444
|
+
}
|
|
2445
|
+
/**
|
|
2446
|
+
* Add field to entity definition
|
|
2447
|
+
*/
|
|
2448
|
+
addField(field) {
|
|
2449
|
+
if ('fields' in this._definition) {
|
|
2450
|
+
this._definition.fields = this._definition.fields || [];
|
|
2451
|
+
this._definition.fields.push(field);
|
|
2452
|
+
}
|
|
2453
|
+
return this;
|
|
2454
|
+
}
|
|
2455
|
+
/**
|
|
2456
|
+
* Set title with transformation
|
|
2457
|
+
*/
|
|
2458
|
+
setTitle(title) {
|
|
2459
|
+
this._definition.title = title;
|
|
2460
|
+
return this;
|
|
2461
|
+
}
|
|
2462
|
+
/**
|
|
2463
|
+
* Apply conditional logic
|
|
2464
|
+
*/
|
|
2465
|
+
when(condition, callback) {
|
|
2466
|
+
if (condition) {
|
|
2467
|
+
callback(this);
|
|
2468
|
+
}
|
|
2469
|
+
return this;
|
|
2470
|
+
}
|
|
2471
|
+
/**
|
|
2472
|
+
* Replace the entire definition
|
|
2473
|
+
*/
|
|
2474
|
+
replaceDefinition(newDefinition) {
|
|
2475
|
+
this._definition = JSON.parse(JSON.stringify(newDefinition));
|
|
2476
|
+
return this;
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
//#endregion
|
|
2480
|
+
|
|
2481
|
+
//#region ---- Domain Registry Service ----
|
|
2482
|
+
/**
|
|
2483
|
+
* Central registry for managing domain definitions, middleware, and dynamic loading.
|
|
2484
|
+
*
|
|
2485
|
+
* Provides:
|
|
2486
|
+
* - Path-based domain resolution (module.aggregate.entity)
|
|
2487
|
+
* - Definition-level caching with fresh model creation
|
|
2488
|
+
* - Type-specific middleware processing
|
|
2489
|
+
* - On-demand domain loading with first-wins strategy
|
|
2490
|
+
* - Model creation and dependency injection
|
|
2491
|
+
*/
|
|
2492
|
+
class AXPDomainRegistry {
|
|
2493
|
+
constructor() {
|
|
2494
|
+
// Cache definitions (before middleware), not models
|
|
2495
|
+
this._definitionCache = new Map();
|
|
2496
|
+
// Type-specific middleware collections
|
|
2497
|
+
this._moduleMiddleware = [];
|
|
2498
|
+
this._aggregateMiddleware = [];
|
|
2499
|
+
this._entityMiddleware = [];
|
|
2500
|
+
// Registered loaders for on-demand loading
|
|
2501
|
+
this._loaders = [];
|
|
2502
|
+
// Injected dependencies
|
|
2503
|
+
this.schemaService = inject(AXPSchemaService);
|
|
2504
|
+
}
|
|
2505
|
+
//#region ---- Main Resolution Method ----
|
|
2506
|
+
/**
|
|
2507
|
+
* Resolve a domain object by path with full middleware processing.
|
|
2508
|
+
* Supports definition-level caching with fresh model creation.
|
|
2509
|
+
*
|
|
2510
|
+
* @param path Domain path (e.g., "user-management.user-aggregate.user")
|
|
2511
|
+
* @returns Promise that resolves to the domain model
|
|
2512
|
+
*/
|
|
2513
|
+
async resolve(path) {
|
|
2514
|
+
const pathInfo = this.parsePath(path);
|
|
2515
|
+
// Check definition cache first
|
|
2516
|
+
let definition = this._definitionCache.get(path);
|
|
2517
|
+
if (!definition) {
|
|
2518
|
+
// Load from first available loader
|
|
2519
|
+
definition = await this.loadFromLoaders(path, pathInfo.type);
|
|
2520
|
+
if (!definition) {
|
|
2521
|
+
throw new Error(`Cannot resolve domain path: ${path}`);
|
|
2522
|
+
}
|
|
2523
|
+
// Cache the raw definition
|
|
2524
|
+
this._definitionCache.set(path, definition);
|
|
2525
|
+
}
|
|
2526
|
+
// Apply type-specific middleware (creates new processed definition)
|
|
2527
|
+
const processedDefinition = await this.applyTypeMiddleware(definition, pathInfo.type, path);
|
|
2528
|
+
// Create and return fresh model instance (not cached)
|
|
2529
|
+
return this.createModel(processedDefinition, pathInfo);
|
|
2530
|
+
}
|
|
2531
|
+
//#endregion
|
|
2532
|
+
//#region ---- Path Parsing ----
|
|
2533
|
+
/**
|
|
2534
|
+
* Parse domain path string into structured information
|
|
2535
|
+
*
|
|
2536
|
+
* @param path Domain path to parse
|
|
2537
|
+
* @returns Parsed path information
|
|
2538
|
+
*/
|
|
2539
|
+
parsePath(path) {
|
|
2540
|
+
const parts = path.split('.');
|
|
2541
|
+
if (parts.length === 1) {
|
|
2542
|
+
return {
|
|
2543
|
+
module: parts[0],
|
|
2544
|
+
type: 'module',
|
|
2545
|
+
fullPath: path
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
else if (parts.length === 2) {
|
|
2549
|
+
return {
|
|
2550
|
+
module: parts[0],
|
|
2551
|
+
aggregate: parts[1],
|
|
2552
|
+
type: 'aggregate',
|
|
2553
|
+
fullPath: path
|
|
2554
|
+
};
|
|
2555
|
+
}
|
|
2556
|
+
else if (parts.length === 3) {
|
|
2557
|
+
return {
|
|
2558
|
+
module: parts[0],
|
|
2559
|
+
aggregate: parts[1],
|
|
2560
|
+
entity: parts[2],
|
|
2561
|
+
type: 'entity',
|
|
2562
|
+
fullPath: path
|
|
2563
|
+
};
|
|
2564
|
+
}
|
|
2565
|
+
throw new Error(`Invalid domain path format: ${path}. Expected: module, module.aggregate, or module.aggregate.entity`);
|
|
2566
|
+
}
|
|
2567
|
+
//#endregion
|
|
2568
|
+
//#region ---- Loader Management ----
|
|
2569
|
+
/**
|
|
2570
|
+
* Register a domain loader for on-demand loading
|
|
2571
|
+
*
|
|
2572
|
+
* @param loader The loader instance to register
|
|
2573
|
+
*/
|
|
2574
|
+
addLoader(loader) {
|
|
2575
|
+
this._loaders.push(loader);
|
|
2576
|
+
// Sort by priority (highest first) after adding
|
|
2577
|
+
this._loaders.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
2578
|
+
}
|
|
2579
|
+
/**
|
|
2580
|
+
* Load definition from registered loaders using first-wins strategy
|
|
2581
|
+
*
|
|
2582
|
+
* @param path Domain path to load
|
|
2583
|
+
* @param type Type of domain object
|
|
2584
|
+
* @returns Promise that resolves to definition or undefined
|
|
2585
|
+
*/
|
|
2586
|
+
async loadFromLoaders(path, type) {
|
|
2587
|
+
for (const loader of this._loaders) {
|
|
2588
|
+
if (loader.canLoad(path, type)) {
|
|
2589
|
+
try {
|
|
2590
|
+
const definition = await loader.load(path, type);
|
|
2591
|
+
if (definition) {
|
|
2592
|
+
console.log(`✓ Loaded ${path} from ${loader.constructor.name}`);
|
|
2593
|
+
return definition; // Return first successful result
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
catch (error) {
|
|
2597
|
+
console.warn(`⚠ Loader ${loader.constructor.name} failed for ${path}:`, error);
|
|
2598
|
+
// Continue to next loader instead of throwing
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
return undefined; // No loader could provide the definition
|
|
2603
|
+
}
|
|
2604
|
+
//#endregion
|
|
2605
|
+
//#region ---- Middleware Management ----
|
|
2606
|
+
/**
|
|
2607
|
+
* Add middleware for module processing
|
|
2608
|
+
*/
|
|
2609
|
+
addModuleMiddleware(middleware) {
|
|
2610
|
+
this._moduleMiddleware.push(middleware);
|
|
2611
|
+
}
|
|
2612
|
+
/**
|
|
2613
|
+
* Add middleware for aggregate processing
|
|
2614
|
+
*/
|
|
2615
|
+
addAggregateMiddleware(middleware) {
|
|
2616
|
+
this._aggregateMiddleware.push(middleware);
|
|
2617
|
+
}
|
|
2618
|
+
/**
|
|
2619
|
+
* Add middleware for entity processing
|
|
2620
|
+
*/
|
|
2621
|
+
addEntityMiddleware(middleware) {
|
|
2622
|
+
this._entityMiddleware.push(middleware);
|
|
2623
|
+
}
|
|
2624
|
+
/**
|
|
2625
|
+
* Apply type-specific middleware to definition
|
|
2626
|
+
*
|
|
2627
|
+
* @param definition Raw definition from cache or loader
|
|
2628
|
+
* @param type Type of domain object
|
|
2629
|
+
* @param path Original path for context
|
|
2630
|
+
* @returns Processed definition
|
|
2631
|
+
*/
|
|
2632
|
+
async applyTypeMiddleware(definition, type, path) {
|
|
2633
|
+
const middleware = this.getMiddlewareForType(type);
|
|
2634
|
+
if (middleware.length === 0) {
|
|
2635
|
+
return definition; // No middleware to apply
|
|
2636
|
+
}
|
|
2637
|
+
const context = new AXPDomainMiddlewareContext(definition, type);
|
|
2638
|
+
// Apply all middleware for this type
|
|
2639
|
+
for (const mw of middleware) {
|
|
2640
|
+
await mw(context);
|
|
2641
|
+
}
|
|
2642
|
+
return context.definition;
|
|
2643
|
+
}
|
|
2644
|
+
/**
|
|
2645
|
+
* Get middleware collection for specific type
|
|
2646
|
+
*
|
|
2647
|
+
* @param type Domain object type
|
|
2648
|
+
* @returns Array of middleware functions
|
|
2649
|
+
*/
|
|
2650
|
+
getMiddlewareForType(type) {
|
|
2651
|
+
switch (type) {
|
|
2652
|
+
case 'module': return this._moduleMiddleware;
|
|
2653
|
+
case 'aggregate': return this._aggregateMiddleware;
|
|
2654
|
+
case 'entity': return this._entityMiddleware;
|
|
2655
|
+
default: return [];
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
//#endregion
|
|
2659
|
+
//#region ---- Model Creation ----
|
|
2660
|
+
/**
|
|
2661
|
+
* Create domain model from processed definition
|
|
2662
|
+
*
|
|
2663
|
+
* @param definition Processed definition (after middleware)
|
|
2664
|
+
* @param pathInfo Parsed path information
|
|
2665
|
+
* @returns Created domain model
|
|
2666
|
+
*/
|
|
2667
|
+
createModel(definition, pathInfo) {
|
|
2668
|
+
switch (pathInfo.type) {
|
|
2669
|
+
case 'module':
|
|
2670
|
+
return new AXPModuleModel(definition);
|
|
2671
|
+
case 'aggregate':
|
|
2672
|
+
// For aggregate, we need to resolve parent module first
|
|
2673
|
+
// This will be simplified when we refactor the models
|
|
2674
|
+
const moduleDefinition = {
|
|
2675
|
+
name: pathInfo.module,
|
|
2676
|
+
title: pathInfo.module,
|
|
2677
|
+
aggregates: [definition]
|
|
2678
|
+
};
|
|
2679
|
+
const parentModule = new AXPModuleModel(moduleDefinition);
|
|
2680
|
+
return new AXPAggregateModel(definition, parentModule);
|
|
2681
|
+
case 'entity':
|
|
2682
|
+
// For entity, we need both module and aggregate parents
|
|
2683
|
+
// This will be simplified when we refactor the models
|
|
2684
|
+
const entityAggregateDefinition = {
|
|
2685
|
+
name: pathInfo.aggregate,
|
|
2686
|
+
title: pathInfo.aggregate || '',
|
|
2687
|
+
entities: [definition],
|
|
2688
|
+
relations: [],
|
|
2689
|
+
validations: [],
|
|
2690
|
+
actions: []
|
|
2691
|
+
};
|
|
2692
|
+
const entityModuleDefinition = {
|
|
2693
|
+
name: pathInfo.module,
|
|
2694
|
+
title: pathInfo.module,
|
|
2695
|
+
aggregates: [entityAggregateDefinition]
|
|
2696
|
+
};
|
|
2697
|
+
const entityModule = new AXPModuleModel(entityModuleDefinition);
|
|
2698
|
+
const entityAggregate = new AXPAggregateModel(entityAggregateDefinition, entityModule);
|
|
2699
|
+
return new AXPEntityModel(definition, entityAggregate, this.schemaService);
|
|
2700
|
+
default:
|
|
2701
|
+
throw new Error(`Unknown domain type: ${pathInfo.type}`);
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
//#endregion
|
|
2705
|
+
//#region ---- Cache Management ----
|
|
2706
|
+
/**
|
|
2707
|
+
* Clear definition cache for a specific path
|
|
2708
|
+
*
|
|
2709
|
+
* @param path Path to clear from cache
|
|
2710
|
+
*/
|
|
2711
|
+
invalidateCache(path) {
|
|
2712
|
+
this._definitionCache.delete(path);
|
|
2713
|
+
}
|
|
2714
|
+
/**
|
|
2715
|
+
* Clear all cached definitions
|
|
2716
|
+
*/
|
|
2717
|
+
clearCache() {
|
|
2718
|
+
this._definitionCache.clear();
|
|
2719
|
+
}
|
|
2720
|
+
/**
|
|
2721
|
+
* Get cache statistics
|
|
2722
|
+
*/
|
|
2723
|
+
getCacheStats() {
|
|
2724
|
+
return {
|
|
2725
|
+
size: this._definitionCache.size,
|
|
2726
|
+
paths: Array.from(this._definitionCache.keys())
|
|
2727
|
+
};
|
|
2728
|
+
}
|
|
2729
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AXPDomainRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2730
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AXPDomainRegistry, providedIn: 'root' }); }
|
|
2731
|
+
}
|
|
2732
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AXPDomainRegistry, decorators: [{
|
|
2733
|
+
type: Injectable,
|
|
2734
|
+
args: [{ providedIn: 'root' }]
|
|
2735
|
+
}] });
|
|
2736
|
+
|
|
2737
|
+
//#region ---- Domain Service ----
|
|
2738
|
+
/**
|
|
2739
|
+
* High-level facade service for domain operations.
|
|
2740
|
+
*
|
|
2741
|
+
* Provides simplified interfaces to the domain registry with additional
|
|
2742
|
+
* business logic and convenience methods for common operations.
|
|
2743
|
+
*
|
|
2744
|
+
* This service acts as the main entry point for domain model resolution
|
|
2745
|
+
* and provides a clean API for consuming applications.
|
|
2746
|
+
*/
|
|
2747
|
+
class AXPDomainService {
|
|
2748
|
+
constructor() {
|
|
2749
|
+
// Injected dependencies
|
|
2750
|
+
this.registry = inject(AXPDomainRegistry);
|
|
2751
|
+
}
|
|
2752
|
+
//#region ---- Module Operations ----
|
|
2753
|
+
/**
|
|
2754
|
+
* Get module by name
|
|
2755
|
+
*
|
|
2756
|
+
* @param name Module name
|
|
2757
|
+
* @returns Promise that resolves to module model
|
|
2758
|
+
*/
|
|
2759
|
+
async getModule(name) {
|
|
2760
|
+
return this.registry.resolve(name);
|
|
2761
|
+
}
|
|
2762
|
+
/**
|
|
2763
|
+
* Check if module exists
|
|
2764
|
+
*
|
|
2765
|
+
* @param name Module name
|
|
2766
|
+
* @returns Promise that resolves to boolean
|
|
2767
|
+
*/
|
|
2768
|
+
async moduleExists(name) {
|
|
2769
|
+
try {
|
|
2770
|
+
await this.getModule(name);
|
|
2771
|
+
return true;
|
|
2772
|
+
}
|
|
2773
|
+
catch {
|
|
2774
|
+
return false;
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
//#endregion
|
|
2778
|
+
//#region ---- Aggregate Operations ----
|
|
2779
|
+
/**
|
|
2780
|
+
* Get aggregate by module and aggregate names
|
|
2781
|
+
*
|
|
2782
|
+
* @param moduleName Module name
|
|
2783
|
+
* @param aggregateName Aggregate name
|
|
2784
|
+
* @returns Promise that resolves to aggregate model
|
|
2785
|
+
*/
|
|
2786
|
+
async getAggregate(moduleName, aggregateName) {
|
|
2787
|
+
const path = `${moduleName}.${aggregateName}`;
|
|
2788
|
+
return this.registry.resolve(path);
|
|
2789
|
+
}
|
|
2790
|
+
/**
|
|
2791
|
+
* Get aggregate by path
|
|
2792
|
+
*
|
|
2793
|
+
* @param path Aggregate path (module.aggregate)
|
|
2794
|
+
* @returns Promise that resolves to aggregate model
|
|
2795
|
+
*/
|
|
2796
|
+
async getAggregateByPath(path) {
|
|
2797
|
+
return this.registry.resolve(path);
|
|
2798
|
+
}
|
|
2799
|
+
/**
|
|
2800
|
+
* Check if aggregate exists
|
|
2801
|
+
*
|
|
2802
|
+
* @param moduleName Module name
|
|
2803
|
+
* @param aggregateName Aggregate name
|
|
2804
|
+
* @returns Promise that resolves to boolean
|
|
2805
|
+
*/
|
|
2806
|
+
async aggregateExists(moduleName, aggregateName) {
|
|
2807
|
+
try {
|
|
2808
|
+
await this.getAggregate(moduleName, aggregateName);
|
|
2809
|
+
return true;
|
|
2810
|
+
}
|
|
2811
|
+
catch {
|
|
2812
|
+
return false;
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
//#endregion
|
|
2816
|
+
//#region ---- Entity Operations ----
|
|
2817
|
+
/**
|
|
2818
|
+
* Get entity by full path
|
|
2819
|
+
*
|
|
2820
|
+
* @param path Full entity path (module.aggregate.entity)
|
|
2821
|
+
* @returns Promise that resolves to entity model
|
|
2822
|
+
*/
|
|
2823
|
+
async getEntity(path) {
|
|
2824
|
+
return this.registry.resolve(path);
|
|
2825
|
+
}
|
|
2826
|
+
/**
|
|
2827
|
+
* Get entity by component parts
|
|
2828
|
+
*
|
|
2829
|
+
* @param moduleName Module name
|
|
2830
|
+
* @param aggregateName Aggregate name
|
|
2831
|
+
* @param entityName Entity name
|
|
2832
|
+
* @returns Promise that resolves to entity model
|
|
2833
|
+
*/
|
|
2834
|
+
async getEntityByParts(moduleName, aggregateName, entityName) {
|
|
2835
|
+
const path = `${moduleName}.${aggregateName}.${entityName}`;
|
|
2836
|
+
return this.getEntity(path);
|
|
2837
|
+
}
|
|
2838
|
+
/**
|
|
2839
|
+
* Check if entity exists
|
|
2840
|
+
*
|
|
2841
|
+
* @param path Full entity path
|
|
2842
|
+
* @returns Promise that resolves to boolean
|
|
2843
|
+
*/
|
|
2844
|
+
async entityExists(path) {
|
|
2845
|
+
try {
|
|
2846
|
+
await this.getEntity(path);
|
|
2847
|
+
return true;
|
|
2848
|
+
}
|
|
2849
|
+
catch {
|
|
2850
|
+
return false;
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
//#endregion
|
|
2854
|
+
//#region ---- Batch Operations ----
|
|
2855
|
+
/**
|
|
2856
|
+
* Get multiple modules in parallel
|
|
2857
|
+
*
|
|
2858
|
+
* @param moduleNames Array of module names
|
|
2859
|
+
* @returns Promise that resolves to array of module models
|
|
2860
|
+
*/
|
|
2861
|
+
async getModules(moduleNames) {
|
|
2862
|
+
return Promise.all(moduleNames.map(name => this.getModule(name)));
|
|
2863
|
+
}
|
|
2864
|
+
/**
|
|
2865
|
+
* Get multiple aggregates in parallel
|
|
2866
|
+
*
|
|
2867
|
+
* @param aggregatePaths Array of aggregate paths
|
|
2868
|
+
* @returns Promise that resolves to array of aggregate models
|
|
2869
|
+
*/
|
|
2870
|
+
async getAggregates(aggregatePaths) {
|
|
2871
|
+
return Promise.all(aggregatePaths.map(path => this.getAggregateByPath(path)));
|
|
2872
|
+
}
|
|
2873
|
+
/**
|
|
2874
|
+
* Get multiple entities in parallel
|
|
2875
|
+
*
|
|
2876
|
+
* @param entityPaths Array of entity paths
|
|
2877
|
+
* @returns Promise that resolves to array of entity models
|
|
2878
|
+
*/
|
|
2879
|
+
async getEntities(entityPaths) {
|
|
2880
|
+
return Promise.all(entityPaths.map(path => this.getEntity(path)));
|
|
2881
|
+
}
|
|
2882
|
+
//#endregion
|
|
2883
|
+
//#region ---- Search and Discovery ----
|
|
2884
|
+
/**
|
|
2885
|
+
* Find entities by name across all loaded modules
|
|
2886
|
+
*
|
|
2887
|
+
* @param entityName Entity name to search for
|
|
2888
|
+
* @returns Promise that resolves to array of matching entity paths
|
|
2889
|
+
*/
|
|
2890
|
+
async findEntitiesByName(entityName) {
|
|
2891
|
+
// This would require additional registry functionality
|
|
2892
|
+
// For now, return empty array as placeholder
|
|
2893
|
+
return [];
|
|
2894
|
+
}
|
|
2895
|
+
/**
|
|
2896
|
+
* Get all aggregates for a module
|
|
2897
|
+
*
|
|
2898
|
+
* @param moduleName Module name
|
|
2899
|
+
* @returns Promise that resolves to array of aggregate models
|
|
2900
|
+
*/
|
|
2901
|
+
async getModuleAggregates(moduleName) {
|
|
2902
|
+
const module = await this.getModule(moduleName);
|
|
2903
|
+
return module.getAllAggregates();
|
|
2904
|
+
}
|
|
2905
|
+
/**
|
|
2906
|
+
* Get all entity references for an aggregate
|
|
2907
|
+
*
|
|
2908
|
+
* @param moduleName Module name
|
|
2909
|
+
* @param aggregateName Aggregate name
|
|
2910
|
+
* @returns Promise that resolves to entity references map
|
|
2911
|
+
*/
|
|
2912
|
+
async getAggregateEntityReferences(moduleName, aggregateName) {
|
|
2913
|
+
const aggregate = await this.getAggregate(moduleName, aggregateName);
|
|
2914
|
+
return aggregate.entityReferences;
|
|
2915
|
+
}
|
|
2916
|
+
//#endregion
|
|
2917
|
+
//#region ---- Validation Operations ----
|
|
2918
|
+
/**
|
|
2919
|
+
* Validate module and all its components
|
|
2920
|
+
*
|
|
2921
|
+
* @param moduleName Module name
|
|
2922
|
+
* @returns Promise that resolves to validation errors array
|
|
2923
|
+
*/
|
|
2924
|
+
async validateModule(moduleName) {
|
|
2925
|
+
try {
|
|
2926
|
+
const module = await this.getModule(moduleName);
|
|
2927
|
+
return module.validate();
|
|
2928
|
+
}
|
|
2929
|
+
catch (error) {
|
|
2930
|
+
return [`Failed to load module '${moduleName}': ${error instanceof Error ? error.message : 'Unknown error'}`];
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
/**
|
|
2934
|
+
* Validate aggregate and its components
|
|
2935
|
+
*
|
|
2936
|
+
* @param moduleName Module name
|
|
2937
|
+
* @param aggregateName Aggregate name
|
|
2938
|
+
* @returns Promise that resolves to validation errors array
|
|
2939
|
+
*/
|
|
2940
|
+
async validateAggregate(moduleName, aggregateName) {
|
|
2941
|
+
try {
|
|
2942
|
+
const aggregate = await this.getAggregate(moduleName, aggregateName);
|
|
2943
|
+
return aggregate.validate();
|
|
2944
|
+
}
|
|
2945
|
+
catch (error) {
|
|
2946
|
+
return [`Failed to load aggregate '${moduleName}.${aggregateName}': ${error instanceof Error ? error.message : 'Unknown error'}`];
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
//#endregion
|
|
2950
|
+
//#region ---- Utility Operations ----
|
|
2951
|
+
/**
|
|
2952
|
+
* Get statistics for a module
|
|
2953
|
+
*
|
|
2954
|
+
* @param moduleName Module name
|
|
2955
|
+
* @returns Promise that resolves to module statistics
|
|
2956
|
+
*/
|
|
2957
|
+
async getModuleStatistics(moduleName) {
|
|
2958
|
+
const module = await this.getModule(moduleName);
|
|
2959
|
+
return module.getStatistics();
|
|
2960
|
+
}
|
|
2961
|
+
/**
|
|
2962
|
+
* Get statistics for an aggregate
|
|
2963
|
+
*
|
|
2964
|
+
* @param moduleName Module name
|
|
2965
|
+
* @param aggregateName Aggregate name
|
|
2966
|
+
* @returns Promise that resolves to aggregate statistics
|
|
2967
|
+
*/
|
|
2968
|
+
async getAggregateStatistics(moduleName, aggregateName) {
|
|
2969
|
+
const aggregate = await this.getAggregate(moduleName, aggregateName);
|
|
2970
|
+
return aggregate.getStatistics();
|
|
2971
|
+
}
|
|
2972
|
+
/**
|
|
2973
|
+
* Clear registry cache
|
|
2974
|
+
*/
|
|
2975
|
+
clearCache() {
|
|
2976
|
+
this.registry.clearCache();
|
|
2977
|
+
}
|
|
2978
|
+
/**
|
|
2979
|
+
* Get cache statistics
|
|
2980
|
+
*
|
|
2981
|
+
* @returns Cache statistics object
|
|
2982
|
+
*/
|
|
2983
|
+
getCacheStatistics() {
|
|
2984
|
+
return this.registry.getCacheStats();
|
|
2985
|
+
}
|
|
2986
|
+
/**
|
|
2987
|
+
* Invalidate cache for specific path
|
|
2988
|
+
*
|
|
2989
|
+
* @param path Domain path to invalidate
|
|
2990
|
+
*/
|
|
2991
|
+
invalidateCache(path) {
|
|
2992
|
+
this.registry.invalidateCache(path);
|
|
2993
|
+
}
|
|
2994
|
+
//#endregion
|
|
2995
|
+
//#region ---- Helper Methods ----
|
|
2996
|
+
/**
|
|
2997
|
+
* Parse domain path into components
|
|
2998
|
+
*
|
|
2999
|
+
* @param path Domain path to parse
|
|
3000
|
+
* @returns Parsed path components
|
|
3001
|
+
*/
|
|
3002
|
+
parsePath(path) {
|
|
3003
|
+
const parts = path.split('.');
|
|
3004
|
+
if (parts.length === 1) {
|
|
3005
|
+
return { module: parts[0], type: 'module' };
|
|
3006
|
+
}
|
|
3007
|
+
else if (parts.length === 2) {
|
|
3008
|
+
return { module: parts[0], aggregate: parts[1], type: 'aggregate' };
|
|
3009
|
+
}
|
|
3010
|
+
else if (parts.length === 3) {
|
|
3011
|
+
return { module: parts[0], aggregate: parts[1], entity: parts[2], type: 'entity' };
|
|
3012
|
+
}
|
|
3013
|
+
throw new Error(`Invalid domain path format: ${path}`);
|
|
3014
|
+
}
|
|
3015
|
+
/**
|
|
3016
|
+
* Build path from components
|
|
3017
|
+
*
|
|
3018
|
+
* @param module Module name
|
|
3019
|
+
* @param aggregate Aggregate name (optional)
|
|
3020
|
+
* @param entity Entity name (optional)
|
|
3021
|
+
* @returns Built path string
|
|
3022
|
+
*/
|
|
3023
|
+
buildPath(module, aggregate, entity) {
|
|
3024
|
+
if (entity && aggregate) {
|
|
3025
|
+
return `${module}.${aggregate}.${entity}`;
|
|
3026
|
+
}
|
|
3027
|
+
else if (aggregate) {
|
|
3028
|
+
return `${module}.${aggregate}`;
|
|
3029
|
+
}
|
|
3030
|
+
else {
|
|
3031
|
+
return module;
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AXPDomainService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3035
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AXPDomainService, providedIn: 'root' }); }
|
|
3036
|
+
}
|
|
3037
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AXPDomainService, decorators: [{
|
|
3038
|
+
type: Injectable,
|
|
3039
|
+
args: [{ providedIn: 'root' }]
|
|
3040
|
+
}] });
|
|
3041
|
+
|
|
3042
|
+
//#endregion
|
|
3043
|
+
|
|
3044
|
+
//#endregion
|
|
3045
|
+
//#region ---- Provider Functions ----
|
|
3046
|
+
/**
|
|
3047
|
+
* Provide domain loaders for on-demand domain loading.
|
|
3048
|
+
*
|
|
3049
|
+
* Domain loaders enable the registry to load domain definitions that are not registered
|
|
3050
|
+
* at build time. This is useful for:
|
|
3051
|
+
* - Loading domain definitions from external APIs
|
|
3052
|
+
* - Dynamic domain generation
|
|
3053
|
+
* - Lazy loading of large domain sets
|
|
3054
|
+
* - Development-time domain hot-reloading
|
|
3055
|
+
*
|
|
3056
|
+
* @param loaders Array of loader classes, instances, or loader configurations
|
|
3057
|
+
* @returns Environment providers for dependency injection
|
|
3058
|
+
*
|
|
3059
|
+
* @example
|
|
3060
|
+
* ```typescript
|
|
3061
|
+
* // Simple loader registration
|
|
3062
|
+
* provideDomainLoaders([
|
|
3063
|
+
* HttpDomainLoader,
|
|
3064
|
+
* FileSystemDomainLoader,
|
|
3065
|
+
* new MemoryDomainLoader()
|
|
3066
|
+
* ])
|
|
3067
|
+
*
|
|
3068
|
+
* // With priority configuration
|
|
3069
|
+
* provideDomainLoaders([
|
|
3070
|
+
* { loader: HttpDomainLoader, priority: 10 },
|
|
3071
|
+
* { loader: FileSystemDomainLoader, priority: 5 },
|
|
3072
|
+
* { loader: new MemoryDomainLoader(), priority: 15 }
|
|
3073
|
+
* ])
|
|
3074
|
+
* ```
|
|
3075
|
+
*/
|
|
3076
|
+
function provideDomainLoaders(loaders) {
|
|
3077
|
+
return makeEnvironmentProviders([
|
|
3078
|
+
{
|
|
3079
|
+
provide: AXP_DOMAIN_LOADER_SETUP,
|
|
3080
|
+
useFactory: async () => {
|
|
3081
|
+
const registry = inject(AXPDomainRegistry);
|
|
3082
|
+
if (!registry) {
|
|
3083
|
+
console.warn('Domain registry not available during loader setup');
|
|
3084
|
+
return false;
|
|
3085
|
+
}
|
|
3086
|
+
// Register each loader with the registry
|
|
3087
|
+
for (const loaderConfig of loaders) {
|
|
3088
|
+
try {
|
|
3089
|
+
let loaderInstance;
|
|
3090
|
+
let priority;
|
|
3091
|
+
if (typeof loaderConfig === 'function') {
|
|
3092
|
+
// Type<AXPDomainLoader>
|
|
3093
|
+
loaderInstance = new loaderConfig();
|
|
3094
|
+
priority = loaderInstance.priority;
|
|
3095
|
+
}
|
|
3096
|
+
else if (typeof loaderConfig === 'object' && 'canLoad' in loaderConfig) {
|
|
3097
|
+
// AXPDomainLoader instance
|
|
3098
|
+
loaderInstance = loaderConfig;
|
|
3099
|
+
priority = loaderInstance.priority;
|
|
3100
|
+
}
|
|
3101
|
+
else if (typeof loaderConfig === 'object' && 'loader' in loaderConfig) {
|
|
3102
|
+
// AXPDomainLoaderConfig
|
|
3103
|
+
if (typeof loaderConfig.loader === 'function') {
|
|
3104
|
+
loaderInstance = new loaderConfig.loader();
|
|
3105
|
+
}
|
|
3106
|
+
else {
|
|
3107
|
+
loaderInstance = loaderConfig.loader;
|
|
3108
|
+
}
|
|
3109
|
+
priority = loaderConfig.priority ?? loaderInstance.priority;
|
|
3110
|
+
}
|
|
3111
|
+
else {
|
|
3112
|
+
console.warn('Invalid loader configuration:', loaderConfig);
|
|
3113
|
+
continue;
|
|
3114
|
+
}
|
|
3115
|
+
// Apply custom priority if specified
|
|
3116
|
+
if (priority !== undefined && priority !== loaderInstance.priority) {
|
|
3117
|
+
loaderInstance.priority = priority;
|
|
3118
|
+
}
|
|
3119
|
+
registry.addLoader(loaderInstance);
|
|
3120
|
+
console.log(`✓ Registered domain loader: ${loaderInstance.constructor.name} (priority: ${loaderInstance.priority ?? 0})`);
|
|
3121
|
+
}
|
|
3122
|
+
catch (error) {
|
|
3123
|
+
console.error('Failed to register domain loader:', error);
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
return true;
|
|
3127
|
+
},
|
|
3128
|
+
multi: true
|
|
3129
|
+
}
|
|
3130
|
+
]);
|
|
3131
|
+
}
|
|
3132
|
+
/**
|
|
3133
|
+
* Provide a single domain loader for convenience
|
|
3134
|
+
*
|
|
3135
|
+
* @param loader Loader class, instance, or configuration
|
|
3136
|
+
* @returns Environment providers for dependency injection
|
|
3137
|
+
*/
|
|
3138
|
+
function provideDomainLoader(loader) {
|
|
3139
|
+
return provideDomainLoaders([loader]);
|
|
3140
|
+
}
|
|
3141
|
+
//#endregion
|
|
3142
|
+
|
|
3143
|
+
//#region ---- Injection Tokens ----
|
|
3144
|
+
/**
|
|
3145
|
+
* Injection tokens for type-specific middleware setup
|
|
3146
|
+
*/
|
|
3147
|
+
const AXP_MODULE_MIDDLEWARE_SETUP = 'AXP_MODULE_MIDDLEWARE_SETUP';
|
|
3148
|
+
const AXP_AGGREGATE_MIDDLEWARE_SETUP = 'AXP_AGGREGATE_MIDDLEWARE_SETUP';
|
|
3149
|
+
const AXP_ENTITY_MIDDLEWARE_SETUP = 'AXP_ENTITY_MIDDLEWARE_SETUP';
|
|
3150
|
+
//#endregion
|
|
3151
|
+
//#region ---- Provider Functions ----
|
|
3152
|
+
/**
|
|
3153
|
+
* Provide middleware for module processing.
|
|
3154
|
+
*
|
|
3155
|
+
* Module middleware is applied when resolving module definitions and can:
|
|
3156
|
+
* - Add metadata and custom properties
|
|
3157
|
+
* - Validate module structure
|
|
3158
|
+
* - Transform module definitions
|
|
3159
|
+
* - Add computed properties
|
|
3160
|
+
*
|
|
3161
|
+
* @param middleware Array of middleware functions to register
|
|
3162
|
+
* @returns Environment providers for dependency injection
|
|
3163
|
+
*
|
|
3164
|
+
* @example
|
|
3165
|
+
* ```typescript
|
|
3166
|
+
* provideModuleMiddleware([
|
|
3167
|
+
* (context) => {
|
|
3168
|
+
* // Add common module metadata
|
|
3169
|
+
* context.setMetadata('loadedAt', new Date());
|
|
3170
|
+
* context.setMetadata('version', '1.0.0');
|
|
3171
|
+
* },
|
|
3172
|
+
* (context) => {
|
|
3173
|
+
* // Add namespace prefix if not present
|
|
3174
|
+
* if (!context.definition.name.includes('.')) {
|
|
3175
|
+
* context.definition.name = `app.${context.definition.name}`;
|
|
3176
|
+
* }
|
|
3177
|
+
* }
|
|
3178
|
+
* ])
|
|
3179
|
+
* ```
|
|
3180
|
+
*/
|
|
3181
|
+
function provideModuleMiddleware(middleware) {
|
|
3182
|
+
return makeEnvironmentProviders([
|
|
3183
|
+
{
|
|
3184
|
+
provide: AXP_MODULE_MIDDLEWARE_SETUP,
|
|
3185
|
+
useFactory: () => {
|
|
3186
|
+
// Access registry through DI when available
|
|
3187
|
+
const registry = globalThis.__domainRegistry__;
|
|
3188
|
+
if (!registry) {
|
|
3189
|
+
console.warn('Domain registry not available during module middleware setup');
|
|
3190
|
+
return false;
|
|
3191
|
+
}
|
|
3192
|
+
// Register all module middleware
|
|
3193
|
+
for (const mw of middleware) {
|
|
3194
|
+
registry.addModuleMiddleware(mw);
|
|
3195
|
+
}
|
|
3196
|
+
console.log(`✓ Registered ${middleware.length} module middleware functions`);
|
|
3197
|
+
return true;
|
|
3198
|
+
},
|
|
3199
|
+
multi: true
|
|
3200
|
+
}
|
|
3201
|
+
]);
|
|
3202
|
+
}
|
|
3203
|
+
/**
|
|
3204
|
+
* Provide middleware for aggregate processing.
|
|
3205
|
+
*
|
|
3206
|
+
* Aggregate middleware is applied when resolving aggregate definitions and can:
|
|
3207
|
+
* - Add metadata and custom properties
|
|
3208
|
+
* - Validate aggregate structure
|
|
3209
|
+
* - Transform entity references
|
|
3210
|
+
* - Add computed relations
|
|
3211
|
+
*
|
|
3212
|
+
* @param middleware Array of middleware functions to register
|
|
3213
|
+
* @returns Environment providers for dependency injection
|
|
3214
|
+
*
|
|
3215
|
+
* @example
|
|
3216
|
+
* ```typescript
|
|
3217
|
+
* provideAggregateMiddleware([
|
|
3218
|
+
* (context) => {
|
|
3219
|
+
* // Add aggregate metadata
|
|
3220
|
+
* context.setMetadata('type', 'business-aggregate');
|
|
3221
|
+
* },
|
|
3222
|
+
* (context) => {
|
|
3223
|
+
* // Validate entity references
|
|
3224
|
+
* const entities = context.definition.entities;
|
|
3225
|
+
* if (Object.keys(entities).length === 0) {
|
|
3226
|
+
* throw new Error('Aggregate must have at least one entity');
|
|
3227
|
+
* }
|
|
3228
|
+
* }
|
|
3229
|
+
* ])
|
|
3230
|
+
* ```
|
|
3231
|
+
*/
|
|
3232
|
+
function provideAggregateMiddleware(middleware) {
|
|
3233
|
+
return makeEnvironmentProviders([
|
|
3234
|
+
{
|
|
3235
|
+
provide: AXP_AGGREGATE_MIDDLEWARE_SETUP,
|
|
3236
|
+
useFactory: () => {
|
|
3237
|
+
// Access registry through DI when available
|
|
3238
|
+
const registry = globalThis.__domainRegistry__;
|
|
3239
|
+
if (!registry) {
|
|
3240
|
+
console.warn('Domain registry not available during aggregate middleware setup');
|
|
3241
|
+
return false;
|
|
3242
|
+
}
|
|
3243
|
+
// Register all aggregate middleware
|
|
3244
|
+
for (const mw of middleware) {
|
|
3245
|
+
registry.addAggregateMiddleware(mw);
|
|
3246
|
+
}
|
|
3247
|
+
console.log(`✓ Registered ${middleware.length} aggregate middleware functions`);
|
|
3248
|
+
return true;
|
|
3249
|
+
},
|
|
3250
|
+
multi: true
|
|
3251
|
+
}
|
|
3252
|
+
]);
|
|
3253
|
+
}
|
|
3254
|
+
/**
|
|
3255
|
+
* Provide middleware for entity processing.
|
|
3256
|
+
*
|
|
3257
|
+
* Entity middleware is applied when resolving entity definitions and can:
|
|
3258
|
+
* - Add auto-generated fields (id, timestamps)
|
|
3259
|
+
* - Apply field transformations
|
|
3260
|
+
* - Add validation rules
|
|
3261
|
+
* - Set default values
|
|
3262
|
+
*
|
|
3263
|
+
* @param middleware Array of middleware functions to register
|
|
3264
|
+
* @returns Environment providers for dependency injection
|
|
3265
|
+
*
|
|
3266
|
+
* @example
|
|
3267
|
+
* ```typescript
|
|
3268
|
+
* provideEntityMiddleware([
|
|
3269
|
+
* (context) => {
|
|
3270
|
+
* // Add auto-generated ID field if not present
|
|
3271
|
+
* if (!context.definition.fields.some(f => f.name === 'id')) {
|
|
3272
|
+
* context.addField({
|
|
3273
|
+
* name: 'id',
|
|
3274
|
+
* type: 'uuid',
|
|
3275
|
+
* required: true,
|
|
3276
|
+
* generated: true
|
|
3277
|
+
* });
|
|
3278
|
+
* }
|
|
3279
|
+
* },
|
|
3280
|
+
* (context) => {
|
|
3281
|
+
* // Add timestamp fields
|
|
3282
|
+
* context.addField({
|
|
3283
|
+
* name: 'createdAt',
|
|
3284
|
+
* type: 'datetime',
|
|
3285
|
+
* required: true,
|
|
3286
|
+
* generated: true
|
|
3287
|
+
* });
|
|
3288
|
+
* context.addField({
|
|
3289
|
+
* name: 'updatedAt',
|
|
3290
|
+
* type: 'datetime',
|
|
3291
|
+
* required: true,
|
|
3292
|
+
* generated: true
|
|
3293
|
+
* });
|
|
3294
|
+
* }
|
|
3295
|
+
* ])
|
|
3296
|
+
* ```
|
|
3297
|
+
*/
|
|
3298
|
+
function provideEntityMiddleware(middleware) {
|
|
3299
|
+
return makeEnvironmentProviders([
|
|
3300
|
+
{
|
|
3301
|
+
provide: AXP_ENTITY_MIDDLEWARE_SETUP,
|
|
3302
|
+
useFactory: () => {
|
|
3303
|
+
// Access registry through DI when available
|
|
3304
|
+
const registry = globalThis.__domainRegistry__;
|
|
3305
|
+
if (!registry) {
|
|
3306
|
+
console.warn('Domain registry not available during entity middleware setup');
|
|
3307
|
+
return false;
|
|
3308
|
+
}
|
|
3309
|
+
// Register all entity middleware
|
|
3310
|
+
for (const mw of middleware) {
|
|
3311
|
+
registry.addEntityMiddleware(mw);
|
|
3312
|
+
}
|
|
3313
|
+
console.log(`✓ Registered ${middleware.length} entity middleware functions`);
|
|
3314
|
+
return true;
|
|
3315
|
+
},
|
|
3316
|
+
multi: true
|
|
3317
|
+
}
|
|
3318
|
+
]);
|
|
3319
|
+
}
|
|
3320
|
+
/**
|
|
3321
|
+
* Provide combined middleware for all domain types.
|
|
3322
|
+
*
|
|
3323
|
+
* This is a convenience function that allows registering middleware for multiple
|
|
3324
|
+
* domain types in a single call.
|
|
3325
|
+
*
|
|
3326
|
+
* @param config Configuration object with middleware for each type
|
|
3327
|
+
* @returns Environment providers for dependency injection
|
|
3328
|
+
*
|
|
3329
|
+
* @example
|
|
3330
|
+
* ```typescript
|
|
3331
|
+
* provideDomainMiddleware({
|
|
3332
|
+
* modules: [
|
|
3333
|
+
* (context) => context.setMetadata('loadedAt', new Date())
|
|
3334
|
+
* ],
|
|
3335
|
+
* aggregates: [
|
|
3336
|
+
* (context) => context.setMetadata('type', 'business-aggregate')
|
|
3337
|
+
* ],
|
|
3338
|
+
* entities: [
|
|
3339
|
+
* (context) => {
|
|
3340
|
+
* if (!context.definition.fields.some(f => f.name === 'id')) {
|
|
3341
|
+
* context.addField({ name: 'id', type: 'uuid', required: true });
|
|
3342
|
+
* }
|
|
3343
|
+
* }
|
|
3344
|
+
* ]
|
|
3345
|
+
* })
|
|
3346
|
+
* ```
|
|
3347
|
+
*/
|
|
3348
|
+
function provideDomainMiddleware(config) {
|
|
3349
|
+
const allProviders = [];
|
|
3350
|
+
// Add module middleware providers
|
|
3351
|
+
if (config.modules && config.modules.length > 0) {
|
|
3352
|
+
allProviders.push({
|
|
3353
|
+
provide: AXP_MODULE_MIDDLEWARE_SETUP,
|
|
3354
|
+
useFactory: () => {
|
|
3355
|
+
const registry = globalThis.__domainRegistry__;
|
|
3356
|
+
if (!registry) {
|
|
3357
|
+
console.warn('Domain registry not available during module middleware setup');
|
|
3358
|
+
return false;
|
|
3359
|
+
}
|
|
3360
|
+
for (const mw of config.modules) {
|
|
3361
|
+
registry.addModuleMiddleware(mw);
|
|
3362
|
+
}
|
|
3363
|
+
return true;
|
|
3364
|
+
},
|
|
3365
|
+
multi: true
|
|
3366
|
+
});
|
|
3367
|
+
}
|
|
3368
|
+
// Add aggregate middleware providers
|
|
3369
|
+
if (config.aggregates && config.aggregates.length > 0) {
|
|
3370
|
+
allProviders.push({
|
|
3371
|
+
provide: AXP_AGGREGATE_MIDDLEWARE_SETUP,
|
|
3372
|
+
useFactory: () => {
|
|
3373
|
+
const registry = globalThis.__domainRegistry__;
|
|
3374
|
+
if (!registry) {
|
|
3375
|
+
console.warn('Domain registry not available during aggregate middleware setup');
|
|
3376
|
+
return false;
|
|
3377
|
+
}
|
|
3378
|
+
for (const mw of config.aggregates) {
|
|
3379
|
+
registry.addAggregateMiddleware(mw);
|
|
3380
|
+
}
|
|
3381
|
+
return true;
|
|
3382
|
+
},
|
|
3383
|
+
multi: true
|
|
3384
|
+
});
|
|
3385
|
+
}
|
|
3386
|
+
// Add entity middleware providers
|
|
3387
|
+
if (config.entities && config.entities.length > 0) {
|
|
3388
|
+
allProviders.push({
|
|
3389
|
+
provide: AXP_ENTITY_MIDDLEWARE_SETUP,
|
|
3390
|
+
useFactory: () => {
|
|
3391
|
+
const registry = globalThis.__domainRegistry__;
|
|
3392
|
+
if (!registry) {
|
|
3393
|
+
console.warn('Domain registry not available during entity middleware setup');
|
|
3394
|
+
return false;
|
|
3395
|
+
}
|
|
3396
|
+
for (const mw of config.entities) {
|
|
3397
|
+
registry.addEntityMiddleware(mw);
|
|
3398
|
+
}
|
|
3399
|
+
return true;
|
|
3400
|
+
},
|
|
3401
|
+
multi: true
|
|
3402
|
+
});
|
|
3403
|
+
}
|
|
3404
|
+
return makeEnvironmentProviders(allProviders);
|
|
3405
|
+
}
|
|
3406
|
+
//#endregion
|
|
3407
|
+
|
|
3408
|
+
//#region ---- Core Registry System ----
|
|
3409
|
+
// Main registry and service
|
|
3410
|
+
//#endregion
|
|
3411
|
+
|
|
3412
|
+
//#endregion
|
|
106
3413
|
|
|
107
3414
|
/**
|
|
108
3415
|
* Generated bundle index. Do not edit.
|
|
109
3416
|
*/
|
|
110
3417
|
|
|
111
|
-
export { AXPDomainModule, AXPEntityCommandScope, AXPEntityType, AXPRelationshipCardinality, AXPRelationshipKind, AXP_ENTITY_CRUD_SETUP, provideEntity };
|
|
3418
|
+
export { AXPAggregateModel, AXPDomainMiddlewareContext, AXPDomainModule, AXPDomainRegistry, AXPDomainService, AXPEntityCommandScope, AXPEntityFieldModel, AXPEntityModel, AXPEntityType, AXPModuleHelper, AXPModuleModel, AXPRelationModel, AXPRelationshipCardinality, AXPRelationshipKind, AXPSchemaMiddlewareContext, AXPSchemaModel, AXPSchemaRegistry, AXPSchemaService, AXP_AGGREGATE_MIDDLEWARE_SETUP, AXP_ENTITY_CRUD_SETUP, AXP_ENTITY_MIDDLEWARE_SETUP, AXP_MODULE_MIDDLEWARE_SETUP, AXP_SCHEMA_EXTENSION, AXP_SCHEMA_LOADER_SETUP, AXP_SCHEMA_MIDDLEWARE_SETUP, AXP_SCHEMA_SETUP, provideAggregateMiddleware, provideDomainLoader, provideDomainLoaders, provideDomainMiddleware, provideEntity, provideEntityMiddleware, provideModuleMiddleware, provideSchema, provideSchemaLoaders, provideSchemaMiddleware, provideSchemaSetups, provideSchemasFromFactory };
|
|
112
3419
|
//# sourceMappingURL=acorex-platform-domain.mjs.map
|