@api-client/core 0.18.48 → 0.18.50
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/build/src/mocking/lib/DataCatalog.d.ts +4 -4
- package/build/src/mocking/lib/DataCatalog.d.ts.map +1 -1
- package/build/src/mocking/lib/DataCatalog.js +8 -8
- package/build/src/mocking/lib/DataCatalog.js.map +1 -1
- package/build/src/mocking/lib/File.d.ts +2 -2
- package/build/src/mocking/lib/File.d.ts.map +1 -1
- package/build/src/mocking/lib/File.js +5 -5
- package/build/src/mocking/lib/File.js.map +1 -1
- package/build/src/mocking/lib/Group.d.ts +1 -1
- package/build/src/mocking/lib/Group.d.ts.map +1 -1
- package/build/src/mocking/lib/Group.js +2 -2
- package/build/src/mocking/lib/Group.js.map +1 -1
- package/build/src/mocking/lib/Invitation.d.ts +1 -1
- package/build/src/mocking/lib/Invitation.d.ts.map +1 -1
- package/build/src/mocking/lib/Invitation.js +2 -2
- package/build/src/mocking/lib/Invitation.js.map +1 -1
- package/build/src/mocking/lib/Organization.d.ts +1 -1
- package/build/src/mocking/lib/Organization.d.ts.map +1 -1
- package/build/src/mocking/lib/Organization.js +2 -2
- package/build/src/mocking/lib/Organization.js.map +1 -1
- package/build/src/mocking/lib/Patch.d.ts +1 -1
- package/build/src/mocking/lib/Patch.d.ts.map +1 -1
- package/build/src/mocking/lib/Patch.js +2 -2
- package/build/src/mocking/lib/Patch.js.map +1 -1
- package/build/src/mocking/lib/Trash.d.ts +1 -1
- package/build/src/mocking/lib/Trash.d.ts.map +1 -1
- package/build/src/mocking/lib/Trash.js +2 -2
- package/build/src/mocking/lib/Trash.js.map +1 -1
- package/build/src/modeling/ApiModel.d.ts +12 -4
- package/build/src/modeling/ApiModel.d.ts.map +1 -1
- package/build/src/modeling/ApiModel.js +76 -31
- package/build/src/modeling/ApiModel.js.map +1 -1
- package/build/src/modeling/ExposedEntity.d.ts +9 -0
- package/build/src/modeling/ExposedEntity.d.ts.map +1 -1
- package/build/src/modeling/ExposedEntity.js +23 -0
- package/build/src/modeling/ExposedEntity.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/data/models/example-generator-api.json +24 -24
- package/package.json +2 -2
- package/src/mocking/README.md +40 -0
- package/src/mocking/lib/DataCatalog.ts +8 -8
- package/src/mocking/lib/File.ts +5 -5
- package/src/mocking/lib/Group.ts +2 -2
- package/src/mocking/lib/Invitation.ts +2 -2
- package/src/mocking/lib/Organization.ts +2 -2
- package/src/mocking/lib/Patch.ts +2 -2
- package/src/mocking/lib/Trash.ts +2 -2
- package/src/modeling/ApiModel.ts +82 -37
- package/src/modeling/ExposedEntity.ts +28 -0
- package/src/models/README.md +5 -4
- package/tests/unit/mocking/current/DataCatalog.spec.ts +28 -0
- package/tests/unit/mocking/current/File.spec.ts +79 -0
- package/tests/unit/mocking/current/Group.spec.ts +49 -0
- package/tests/unit/mocking/current/Invitation.spec.ts +49 -0
- package/tests/unit/mocking/current/Organization.spec.ts +50 -0
- package/tests/unit/mocking/current/Patch.spec.ts +47 -0
- package/tests/unit/mocking/current/Trash.spec.ts +49 -0
- package/tests/unit/modeling/api_model.spec.ts +20 -0
- package/tests/unit/modeling/api_model_expose_entity.spec.ts +25 -0
- package/tests/unit/modeling/api_model_remove_entity.spec.ts +17 -10
- package/tests/unit/modeling/exposed_entity_setter_validation.spec.ts +107 -0
|
@@ -42062,9 +42062,6 @@
|
|
|
42062
42062
|
"@id": "#209"
|
|
42063
42063
|
},
|
|
42064
42064
|
{
|
|
42065
|
-
"@id": "#206"
|
|
42066
|
-
},
|
|
42067
|
-
{
|
|
42068
42065
|
"@id": "#191"
|
|
42069
42066
|
},
|
|
42070
42067
|
{
|
|
@@ -42080,6 +42077,9 @@
|
|
|
42080
42077
|
"@id": "#203"
|
|
42081
42078
|
},
|
|
42082
42079
|
{
|
|
42080
|
+
"@id": "#206"
|
|
42081
|
+
},
|
|
42082
|
+
{
|
|
42083
42083
|
"@id": "#209"
|
|
42084
42084
|
}
|
|
42085
42085
|
],
|
|
@@ -42813,13 +42813,13 @@
|
|
|
42813
42813
|
"@id": "#210"
|
|
42814
42814
|
},
|
|
42815
42815
|
{
|
|
42816
|
-
"@id": "#
|
|
42816
|
+
"@id": "#219"
|
|
42817
42817
|
},
|
|
42818
42818
|
{
|
|
42819
|
-
"@id": "#
|
|
42819
|
+
"@id": "#213"
|
|
42820
42820
|
},
|
|
42821
42821
|
{
|
|
42822
|
-
"@id": "#
|
|
42822
|
+
"@id": "#216"
|
|
42823
42823
|
}
|
|
42824
42824
|
],
|
|
42825
42825
|
"doc:root": false,
|
|
@@ -43436,7 +43436,7 @@
|
|
|
43436
43436
|
"doc:ExternalDomainElement",
|
|
43437
43437
|
"doc:DomainElement"
|
|
43438
43438
|
],
|
|
43439
|
-
"doc:raw": "
|
|
43439
|
+
"doc:raw": "countryCode: \"BE\"\ngraydonEnterpriseId: 1057155523\nregistrationId: \"0422319093\"\nvatNumber: \"BE0422319093\"\ngraydonCompanyId: \"0422319093\"\nisBranchOffice: false\n",
|
|
43440
43440
|
"core:mediaType": "application/yaml",
|
|
43441
43441
|
"sourcemaps:sources": [
|
|
43442
43442
|
{
|
|
@@ -43457,7 +43457,7 @@
|
|
|
43457
43457
|
"doc:ExternalDomainElement",
|
|
43458
43458
|
"doc:DomainElement"
|
|
43459
43459
|
],
|
|
43460
|
-
"doc:raw": "
|
|
43460
|
+
"doc:raw": "addressType: 'REGISTERED-OFFICE-ADDRESS'\nstreetName: 'UITBREIDINGSTRAAT'\nhouseNumber: '84'\nhouseNumberAddition: '/1'\npostalCode: '2600'\ncity: 'BERCHEM (ANTWERPEN)'\ncountry: 'Belgium'\ncountryCode: 'BE'\nfullFormatedAddress: \"UITBREIDINGSTRAAT 84 /1, 2600 BERCHEM (ANTWERPEN), BELIUM\"\n",
|
|
43461
43461
|
"core:mediaType": "application/yaml",
|
|
43462
43462
|
"sourcemaps:sources": [
|
|
43463
43463
|
{
|
|
@@ -43478,7 +43478,7 @@
|
|
|
43478
43478
|
"doc:ExternalDomainElement",
|
|
43479
43479
|
"doc:DomainElement"
|
|
43480
43480
|
],
|
|
43481
|
-
"doc:raw": "
|
|
43481
|
+
"doc:raw": "code: '5'\ndescription: 'Limited company'\n",
|
|
43482
43482
|
"core:mediaType": "application/yaml",
|
|
43483
43483
|
"sourcemaps:sources": [
|
|
43484
43484
|
{
|
|
@@ -43499,7 +43499,7 @@
|
|
|
43499
43499
|
"doc:ExternalDomainElement",
|
|
43500
43500
|
"doc:DomainElement"
|
|
43501
43501
|
],
|
|
43502
|
-
"doc:raw": "
|
|
43502
|
+
"doc:raw": "class: '3'\ndescription: '150 - 300'\nnumberOfFte: 5500\nnumberOfEmployees: 5232\n",
|
|
43503
43503
|
"core:mediaType": "application/yaml",
|
|
43504
43504
|
"sourcemaps:sources": [
|
|
43505
43505
|
{
|
|
@@ -43520,7 +43520,7 @@
|
|
|
43520
43520
|
"doc:ExternalDomainElement",
|
|
43521
43521
|
"doc:DomainElement"
|
|
43522
43522
|
],
|
|
43523
|
-
"doc:raw": "code: '
|
|
43523
|
+
"doc:raw": "code: 'J'\ndescription: 'Information and communication'\n",
|
|
43524
43524
|
"core:mediaType": "application/yaml",
|
|
43525
43525
|
"sourcemaps:sources": [
|
|
43526
43526
|
{
|
|
@@ -43541,7 +43541,7 @@
|
|
|
43541
43541
|
"doc:ExternalDomainElement",
|
|
43542
43542
|
"doc:DomainElement"
|
|
43543
43543
|
],
|
|
43544
|
-
"doc:raw": "
|
|
43544
|
+
"doc:raw": "code: '7487'\ndescription: 'Financial and insurance activities'\ntype: \"PRIMARY\"\nclassificationCode: 'BE_NACEBEL2008'\nactivityGroupCode: 'ABCDE'\n",
|
|
43545
43545
|
"core:mediaType": "application/yaml",
|
|
43546
43546
|
"sourcemaps:sources": [
|
|
43547
43547
|
{
|
|
@@ -44253,7 +44253,7 @@
|
|
|
44253
44253
|
"doc:ExternalDomainElement",
|
|
44254
44254
|
"doc:DomainElement"
|
|
44255
44255
|
],
|
|
44256
|
-
"doc:raw": "type: 'GENERAL'\
|
|
44256
|
+
"doc:raw": "-\n type: 'GENERAL'\n value: 'info@company.be'\n-\n type: 'IT_DEPT'\n value: 'it-service@company.be'\n",
|
|
44257
44257
|
"core:mediaType": "application/yaml",
|
|
44258
44258
|
"sourcemaps:sources": [
|
|
44259
44259
|
{
|
|
@@ -44274,7 +44274,7 @@
|
|
|
44274
44274
|
"doc:ExternalDomainElement",
|
|
44275
44275
|
"doc:DomainElement"
|
|
44276
44276
|
],
|
|
44277
|
-
"doc:raw": "
|
|
44277
|
+
"doc:raw": "type: \"GENERAL\"\nvalue: \"www.company.be\"\n",
|
|
44278
44278
|
"core:mediaType": "application/yaml",
|
|
44279
44279
|
"sourcemaps:sources": [
|
|
44280
44280
|
{
|
|
@@ -44295,7 +44295,7 @@
|
|
|
44295
44295
|
"doc:ExternalDomainElement",
|
|
44296
44296
|
"doc:DomainElement"
|
|
44297
44297
|
],
|
|
44298
|
-
"doc:raw": "type:
|
|
44298
|
+
"doc:raw": "type: 'GENERAL'\ncountryDialCode : '+32'\nareaCode : '21'\nsubscriberNumber: '12.87.00'\nformatted: '+32-(0)21 302099'\n",
|
|
44299
44299
|
"core:mediaType": "application/yaml",
|
|
44300
44300
|
"sourcemaps:sources": [
|
|
44301
44301
|
{
|
|
@@ -44756,32 +44756,32 @@
|
|
|
44756
44756
|
{
|
|
44757
44757
|
"@id": "#193/source-map/lexical/element_0",
|
|
44758
44758
|
"sourcemaps:element": "amf://id#193",
|
|
44759
|
-
"sourcemaps:value": "[(1,0)-(
|
|
44759
|
+
"sourcemaps:value": "[(1,0)-(7,0)]"
|
|
44760
44760
|
},
|
|
44761
44761
|
{
|
|
44762
44762
|
"@id": "#196/source-map/lexical/element_0",
|
|
44763
44763
|
"sourcemaps:element": "amf://id#196",
|
|
44764
|
-
"sourcemaps:value": "[(1,0)-(
|
|
44764
|
+
"sourcemaps:value": "[(1,0)-(10,0)]"
|
|
44765
44765
|
},
|
|
44766
44766
|
{
|
|
44767
44767
|
"@id": "#199/source-map/lexical/element_0",
|
|
44768
44768
|
"sourcemaps:element": "amf://id#199",
|
|
44769
|
-
"sourcemaps:value": "[(1,0)-(
|
|
44769
|
+
"sourcemaps:value": "[(1,0)-(3,0)]"
|
|
44770
44770
|
},
|
|
44771
44771
|
{
|
|
44772
44772
|
"@id": "#202/source-map/lexical/element_0",
|
|
44773
44773
|
"sourcemaps:element": "amf://id#202",
|
|
44774
|
-
"sourcemaps:value": "[(1,0)-(
|
|
44774
|
+
"sourcemaps:value": "[(1,0)-(5,0)]"
|
|
44775
44775
|
},
|
|
44776
44776
|
{
|
|
44777
44777
|
"@id": "#205/source-map/lexical/element_0",
|
|
44778
44778
|
"sourcemaps:element": "amf://id#205",
|
|
44779
|
-
"sourcemaps:value": "[(1,0)-(
|
|
44779
|
+
"sourcemaps:value": "[(1,0)-(3,0)]"
|
|
44780
44780
|
},
|
|
44781
44781
|
{
|
|
44782
44782
|
"@id": "#208/source-map/lexical/element_0",
|
|
44783
44783
|
"sourcemaps:element": "amf://id#208",
|
|
44784
|
-
"sourcemaps:value": "[(1,0)-(
|
|
44784
|
+
"sourcemaps:value": "[(1,0)-(6,0)]"
|
|
44785
44785
|
},
|
|
44786
44786
|
{
|
|
44787
44787
|
"@id": "#223/source-map/lexical/element_0",
|
|
@@ -45121,17 +45121,17 @@
|
|
|
45121
45121
|
{
|
|
45122
45122
|
"@id": "#215/source-map/lexical/element_0",
|
|
45123
45123
|
"sourcemaps:element": "amf://id#215",
|
|
45124
|
-
"sourcemaps:value": "[(1,0)-(
|
|
45124
|
+
"sourcemaps:value": "[(1,0)-(7,0)]"
|
|
45125
45125
|
},
|
|
45126
45126
|
{
|
|
45127
45127
|
"@id": "#218/source-map/lexical/element_0",
|
|
45128
45128
|
"sourcemaps:element": "amf://id#218",
|
|
45129
|
-
"sourcemaps:value": "[(1,0)-(
|
|
45129
|
+
"sourcemaps:value": "[(1,0)-(3,0)]"
|
|
45130
45130
|
},
|
|
45131
45131
|
{
|
|
45132
45132
|
"@id": "#221/source-map/lexical/element_0",
|
|
45133
45133
|
"sourcemaps:element": "amf://id#221",
|
|
45134
|
-
"sourcemaps:value": "[(1,0)-(
|
|
45134
|
+
"sourcemaps:value": "[(1,0)-(6,0)]"
|
|
45135
45135
|
},
|
|
45136
45136
|
{
|
|
45137
45137
|
"@id": "#338/source-map/synthesized-field/element_1",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@api-client/core",
|
|
3
3
|
"description": "The API Client's core client library. Works in NodeJS and in a ES enabled browser.",
|
|
4
|
-
"version": "0.18.
|
|
4
|
+
"version": "0.18.50",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"exports": {
|
|
7
7
|
"./browser.js": {
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"chalk": "^5.4.1",
|
|
100
100
|
"console-table-printer": "^2.11.2",
|
|
101
101
|
"dompurify": "^3.2.6",
|
|
102
|
-
"jsdom": "^
|
|
102
|
+
"jsdom": "^28.0.0",
|
|
103
103
|
"nanoid": "^5.1.5",
|
|
104
104
|
"tslog": "^4.9.3",
|
|
105
105
|
"ws": "^8.12.0",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Mocking Library (`src/mocking`)
|
|
2
|
+
|
|
3
|
+
This directory contains the mocking infrastructure for the API Cloud Core. It provides realistic data generation for testing and demo purposes without requiring a real backend.
|
|
4
|
+
|
|
5
|
+
## Key Components
|
|
6
|
+
|
|
7
|
+
### `ProjectMock`
|
|
8
|
+
The main entry point for mocking a project environment. It aggregates various sub-mocks to simulate a complete project context.
|
|
9
|
+
|
|
10
|
+
**Usage:**
|
|
11
|
+
```typescript
|
|
12
|
+
import { ProjectMock } from './ProjectMock';
|
|
13
|
+
|
|
14
|
+
const mock = new ProjectMock();
|
|
15
|
+
// Access sub-mocks
|
|
16
|
+
console.log(mock.user);
|
|
17
|
+
console.log(mock.projectRequest);
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### `ModelingMock` & `LegacyMock`
|
|
21
|
+
Specialized mocks for specific domains:
|
|
22
|
+
- `ModelingMock`: Logic for API domain modeling.
|
|
23
|
+
- `LegacyMock`: Support for older systems or deprecated interfaces.
|
|
24
|
+
|
|
25
|
+
## Directory Structure
|
|
26
|
+
|
|
27
|
+
- **`lib/`**: Contains the individual mock implementations.
|
|
28
|
+
- `DataCatalog.ts`: Generates `DataCatalogSchema` objects using `faker`.
|
|
29
|
+
- `File.ts`, `User.ts`, `Request.ts`: Other domain-specific entities.
|
|
30
|
+
- **Pattern**: Classes in `lib/` usually provide methods like `entityName(init)` and `entityNames(size, init)` to generate single or multiple mock objects.
|
|
31
|
+
- **`legacy/`**: Old mock implementations kept for backward compatibility. Use with caution.
|
|
32
|
+
|
|
33
|
+
## Developing Mocks
|
|
34
|
+
|
|
35
|
+
When adding a new mock:
|
|
36
|
+
1. Create a class in `lib/` (e.g., `NewEntity.ts`).
|
|
37
|
+
2. Use `@faker-js/faker` for realistic random data.
|
|
38
|
+
3. Allow overrides via a partial `init` parameter.
|
|
39
|
+
4. Export single and list generation methods.
|
|
40
|
+
5. Register it in `ProjectMock.ts` if it belongs to the global project context.
|
|
@@ -53,10 +53,10 @@ export class DataCatalog {
|
|
|
53
53
|
* @param size Number of data catalogs to generate. Default is 25.
|
|
54
54
|
* @returns List of random data catalogs
|
|
55
55
|
*/
|
|
56
|
-
dataCatalogs(size = 25): DataCatalogSchema[] {
|
|
56
|
+
dataCatalogs(size = 25, init?: Partial<DataCatalogSchema>): DataCatalogSchema[] {
|
|
57
57
|
const result = []
|
|
58
58
|
for (let i = 0; i < size; i++) {
|
|
59
|
-
result.push(this.dataCatalog())
|
|
59
|
+
result.push(this.dataCatalog(init))
|
|
60
60
|
}
|
|
61
61
|
return result
|
|
62
62
|
}
|
|
@@ -97,10 +97,10 @@ export class DataCatalog {
|
|
|
97
97
|
* @param size Number of data catalog versions to generate. Default is 25.
|
|
98
98
|
* @returns List of random data catalog versions.
|
|
99
99
|
*/
|
|
100
|
-
dataCatalogVersions(size = 25): DataCatalogVersionSchema[] {
|
|
100
|
+
dataCatalogVersions(size = 25, init?: Partial<DataCatalogVersionSchema>): DataCatalogVersionSchema[] {
|
|
101
101
|
const result = []
|
|
102
102
|
for (let i = 0; i < size; i++) {
|
|
103
|
-
result.push(this.dataCatalogVersion())
|
|
103
|
+
result.push(this.dataCatalogVersion(init))
|
|
104
104
|
}
|
|
105
105
|
return result
|
|
106
106
|
}
|
|
@@ -129,10 +129,10 @@ export class DataCatalog {
|
|
|
129
129
|
* @param size Number of data catalogs to generate. Default is 3.
|
|
130
130
|
* @returns List of random data catalogs with versions.
|
|
131
131
|
*/
|
|
132
|
-
dataCatalogsWithVersion(size = 3): DataCatalogSchemaWithVersion[] {
|
|
132
|
+
dataCatalogsWithVersion(size = 3, init?: Partial<DataCatalogSchemaWithVersion>): DataCatalogSchemaWithVersion[] {
|
|
133
133
|
const result = []
|
|
134
134
|
for (let i = 0; i < size; i++) {
|
|
135
|
-
result.push(this.dataCatalogWithVersion())
|
|
135
|
+
result.push(this.dataCatalogWithVersion(init))
|
|
136
136
|
}
|
|
137
137
|
return result
|
|
138
138
|
}
|
|
@@ -156,10 +156,10 @@ export class DataCatalog {
|
|
|
156
156
|
* @param size Number of data catalog version info objects to generate. Default is 5.
|
|
157
157
|
* @returns List of random data catalog version info objects.
|
|
158
158
|
*/
|
|
159
|
-
versionInfos(size = 5): DataCatalogVersionInfo[] {
|
|
159
|
+
versionInfos(size = 5, init?: Partial<DataCatalogVersionInfo>): DataCatalogVersionInfo[] {
|
|
160
160
|
const result = []
|
|
161
161
|
for (let i = 0; i < size; i++) {
|
|
162
|
-
result.push(this.versionInfo())
|
|
162
|
+
result.push(this.versionInfo(init))
|
|
163
163
|
}
|
|
164
164
|
return result
|
|
165
165
|
}
|
package/src/mocking/lib/File.ts
CHANGED
|
@@ -15,7 +15,7 @@ export class File {
|
|
|
15
15
|
kind = faker.helpers.arrayElement([ProjectKind, DomainFileKind, CertificateFileKind, ApiFileKind] as const),
|
|
16
16
|
info = { name: faker.system.fileName() },
|
|
17
17
|
} = init
|
|
18
|
-
return CoreFile.createSchema({ kind, info })
|
|
18
|
+
return CoreFile.createSchema({ ...init, kind, info })
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -23,10 +23,10 @@ export class File {
|
|
|
23
23
|
* @param size Number of files to generate. Default is 25.
|
|
24
24
|
* @returns List of random files
|
|
25
25
|
*/
|
|
26
|
-
files(size = 25): IFile[] {
|
|
26
|
+
files(size = 25, init?: Partial<IFile>): IFile[] {
|
|
27
27
|
const result = []
|
|
28
28
|
for (let i = 0; i < size; i++) {
|
|
29
|
-
result.push(this.file())
|
|
29
|
+
result.push(this.file(init))
|
|
30
30
|
}
|
|
31
31
|
return result
|
|
32
32
|
}
|
|
@@ -45,10 +45,10 @@ export class File {
|
|
|
45
45
|
* @param size Number of folders to generate. Default is 25.
|
|
46
46
|
* @returns List of random folders
|
|
47
47
|
*/
|
|
48
|
-
folders(size = 25): IFile[] {
|
|
48
|
+
folders(size = 25, init?: Partial<IFile>): IFile[] {
|
|
49
49
|
const result = []
|
|
50
50
|
for (let i = 0; i < size; i++) {
|
|
51
|
-
result.push(this.folder())
|
|
51
|
+
result.push(this.folder(init))
|
|
52
52
|
}
|
|
53
53
|
return result
|
|
54
54
|
}
|
package/src/mocking/lib/Group.ts
CHANGED
|
@@ -42,10 +42,10 @@ export class Group {
|
|
|
42
42
|
* @param size Number of organizations to generate. Default is 25.
|
|
43
43
|
* @returns List of random organizations
|
|
44
44
|
*/
|
|
45
|
-
groups(size = 25): GroupSchema[] {
|
|
45
|
+
groups(size = 25, init?: Partial<GroupSchema>): GroupSchema[] {
|
|
46
46
|
const result = []
|
|
47
47
|
for (let i = 0; i < size; i++) {
|
|
48
|
-
result.push(this.group())
|
|
48
|
+
result.push(this.group(init))
|
|
49
49
|
}
|
|
50
50
|
return result
|
|
51
51
|
}
|
|
@@ -48,10 +48,10 @@ export class Invitation {
|
|
|
48
48
|
* @param size Number of invitations to generate. Default is 25.
|
|
49
49
|
* @returns List of random invitations
|
|
50
50
|
*/
|
|
51
|
-
invitations(size = 25): InvitationSchema[] {
|
|
51
|
+
invitations(size = 25, init?: Partial<InvitationSchema>): InvitationSchema[] {
|
|
52
52
|
const result = []
|
|
53
53
|
for (let i = 0; i < size; i++) {
|
|
54
|
-
result.push(this.invitation())
|
|
54
|
+
result.push(this.invitation(init))
|
|
55
55
|
}
|
|
56
56
|
return result
|
|
57
57
|
}
|
|
@@ -32,10 +32,10 @@ export class Organization {
|
|
|
32
32
|
* @param size Number of organizations to generate. Default is 25.
|
|
33
33
|
* @returns List of random organizations
|
|
34
34
|
*/
|
|
35
|
-
organizations(size = 25): IOrganization[] {
|
|
35
|
+
organizations(size = 25, init?: Partial<IOrganization>): IOrganization[] {
|
|
36
36
|
const result = []
|
|
37
37
|
for (let i = 0; i < size; i++) {
|
|
38
|
-
result.push(this.organization())
|
|
38
|
+
result.push(this.organization(init))
|
|
39
39
|
}
|
|
40
40
|
return result
|
|
41
41
|
}
|
package/src/mocking/lib/Patch.ts
CHANGED
|
@@ -41,10 +41,10 @@ export class Patch {
|
|
|
41
41
|
* @param size Number of invitations to generate. Default is 25.
|
|
42
42
|
* @returns List of random invitations
|
|
43
43
|
*/
|
|
44
|
-
mediaPatchRevisions(size = 25): MediaPatchRevision[] {
|
|
44
|
+
mediaPatchRevisions(size = 25, init?: Partial<MediaPatchRevision>): MediaPatchRevision[] {
|
|
45
45
|
const result = []
|
|
46
46
|
for (let i = 0; i < size; i++) {
|
|
47
|
-
result.push(this.mediaPatchRevision())
|
|
47
|
+
result.push(this.mediaPatchRevision(init))
|
|
48
48
|
}
|
|
49
49
|
return result
|
|
50
50
|
}
|
package/src/mocking/lib/Trash.ts
CHANGED
|
@@ -37,10 +37,10 @@ export class Trash {
|
|
|
37
37
|
* @param size Number of invitations to generate. Default is 25.
|
|
38
38
|
* @returns List of random invitations
|
|
39
39
|
*/
|
|
40
|
-
trashEntries(size = 25): CoreTrashEntry[] {
|
|
40
|
+
trashEntries(size = 25, init?: Partial<CoreTrashEntry>): CoreTrashEntry[] {
|
|
41
41
|
const result = []
|
|
42
42
|
for (let i = 0; i < size; i++) {
|
|
43
|
-
result.push(this.trashEntry())
|
|
43
|
+
result.push(this.trashEntry(init))
|
|
44
44
|
}
|
|
45
45
|
return result
|
|
46
46
|
}
|
package/src/modeling/ApiModel.ts
CHANGED
|
@@ -280,6 +280,16 @@ export class ApiModel extends DependentModel {
|
|
|
280
280
|
} else if (domain) {
|
|
281
281
|
throw new Error(`Invalid domain provided. Expected a DataDomain instance or schema.`)
|
|
282
282
|
}
|
|
283
|
+
// Note that since we're using the `DependentModel` class, but the API Model can have only one dependency,
|
|
284
|
+
// we keep the reference to the data domain under the `dependencyList` property.
|
|
285
|
+
// It is all handled by the parent class `DependentModel`. This way we simplify the dependency management (loading)
|
|
286
|
+
// process when the API model is loaded from the API.
|
|
287
|
+
if (domain) {
|
|
288
|
+
if (!domain.info.version) {
|
|
289
|
+
throw new Error(`Domain ${domain.key} must have a version.`)
|
|
290
|
+
}
|
|
291
|
+
init.dependencyList = [{ key: domain.key, version: domain.info.version }]
|
|
292
|
+
}
|
|
283
293
|
super(init.dependencyList, instances)
|
|
284
294
|
this.kind = init.kind
|
|
285
295
|
this.key = init.key
|
|
@@ -380,6 +390,12 @@ export class ApiModel extends DependentModel {
|
|
|
380
390
|
/**
|
|
381
391
|
* Exposes a new entity in the API model.
|
|
382
392
|
* If the entity already exists, it returns the existing one.
|
|
393
|
+
*
|
|
394
|
+
* The logic regarding exposing an entity:
|
|
395
|
+
* - If the entity is already exposed as a root entity, it returns the existing one.
|
|
396
|
+
* - If the entity has an association, we expose that entity as a nested entity (by setting the parent property).
|
|
397
|
+
* - If the associated entity is already exposed as a root entity, we do not follow the association.
|
|
398
|
+
*
|
|
383
399
|
* @param entity The entity key and domain to expose.
|
|
384
400
|
* @returns The exposed entity.
|
|
385
401
|
*/
|
|
@@ -403,8 +419,18 @@ export class ApiModel extends DependentModel {
|
|
|
403
419
|
}
|
|
404
420
|
const name = domainEntity.info.name || ''
|
|
405
421
|
const segment = pluralize(name.toLocaleLowerCase())
|
|
406
|
-
|
|
407
|
-
|
|
422
|
+
let relativeCollectionPath = `/${segment}`
|
|
423
|
+
let relativeResourcePath = `/${segment}/{id}`
|
|
424
|
+
|
|
425
|
+
// Check for root path collision and resolve by appending a number
|
|
426
|
+
let counter = 1
|
|
427
|
+
const originalCollectionPath = relativeCollectionPath
|
|
428
|
+
while (this.exposes.some((e) => e.isRoot && e.collectionPath === relativeCollectionPath)) {
|
|
429
|
+
relativeCollectionPath = `${originalCollectionPath}-${counter}`
|
|
430
|
+
relativeResourcePath = `${relativeCollectionPath}/{id}`
|
|
431
|
+
counter++
|
|
432
|
+
}
|
|
433
|
+
|
|
408
434
|
const newEntity: ExposedEntitySchema = {
|
|
409
435
|
kind: ExposedEntityKind,
|
|
410
436
|
key: nanoid(),
|
|
@@ -444,12 +470,7 @@ export class ApiModel extends DependentModel {
|
|
|
444
470
|
return
|
|
445
471
|
}
|
|
446
472
|
const maxDepth = options.maxDepth ?? 6
|
|
447
|
-
const
|
|
448
|
-
// Add parent entity's key to the visited set so we won't skip it when traversing
|
|
449
|
-
// associations.
|
|
450
|
-
visited.add(createDomainKey(parentExposure.entity))
|
|
451
|
-
|
|
452
|
-
const follow = (currentEntity: AssociationTarget, parentKey: string, depth: number) => {
|
|
473
|
+
const follow = (currentEntity: AssociationTarget, parentKey: string, depth: number, currentPath: string[]) => {
|
|
453
474
|
// Find the domain entity
|
|
454
475
|
const domainEntity = domain.findEntity(currentEntity.key, currentEntity.domain)
|
|
455
476
|
if (!domainEntity) return
|
|
@@ -462,24 +483,45 @@ export class ApiModel extends DependentModel {
|
|
|
462
483
|
continue
|
|
463
484
|
}
|
|
464
485
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
if (
|
|
468
|
-
continue
|
|
486
|
+
const targetKeys = createDomainKey(target)
|
|
487
|
+
// Circular dependency check: if this entity is already in our *current* traversal path
|
|
488
|
+
if (currentPath.includes(targetKeys)) {
|
|
489
|
+
continue
|
|
469
490
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
491
|
+
|
|
492
|
+
// Check if this entity is ALREADY exposed anywhere in the model
|
|
493
|
+
const existingExposure = this.getExposedEntity(target)
|
|
494
|
+
|
|
495
|
+
if (existingExposure) {
|
|
496
|
+
// If it's already exposed and NOT root (i.e., it's currently a nested child of someone else),
|
|
497
|
+
// promote it to ROOT to avoid duplicating it or deeply nesting it in multiple places.
|
|
498
|
+
if (!existingExposure.isRoot) {
|
|
499
|
+
// 1. Calculate new root paths (handling collisions)
|
|
500
|
+
const targetDomEntity = domain.findEntity(target.key, target.domain)
|
|
501
|
+
if (targetDomEntity) {
|
|
502
|
+
const name = targetDomEntity.info.name || ''
|
|
503
|
+
const segment = pluralize(name.toLocaleLowerCase())
|
|
504
|
+
let relativeCollectionPath = `/${segment}`
|
|
505
|
+
let relativeResourcePath = `/${segment}/{id}`
|
|
506
|
+
|
|
507
|
+
let counter = 1
|
|
508
|
+
const originalCollectionPath = relativeCollectionPath
|
|
509
|
+
while (this.exposes.some((e) => e.isRoot && e.collectionPath === relativeCollectionPath)) {
|
|
510
|
+
relativeCollectionPath = `${originalCollectionPath}-${counter}`
|
|
511
|
+
relativeResourcePath = `${relativeCollectionPath}/{id}`
|
|
512
|
+
counter++
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// 2. Update properties to make it root
|
|
516
|
+
existingExposure.isRoot = true
|
|
517
|
+
existingExposure.parent = undefined
|
|
518
|
+
existingExposure.collectionPath = relativeCollectionPath
|
|
519
|
+
existingExposure.resourcePath = relativeResourcePath
|
|
520
|
+
existingExposure.hasCollection = true
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
// Whether it was already root or we just promoted it, we stop following here.
|
|
524
|
+
continue
|
|
483
525
|
}
|
|
484
526
|
|
|
485
527
|
// Find the target domain entity for path generation
|
|
@@ -516,27 +558,30 @@ export class ApiModel extends DependentModel {
|
|
|
516
558
|
nestedExposure.truncated = true
|
|
517
559
|
} else {
|
|
518
560
|
// Recursively follow associations
|
|
519
|
-
follow(target, nestedExposure.key, depth + 1)
|
|
561
|
+
follow(target, nestedExposure.key, depth + 1, [...currentPath, targetKeys])
|
|
520
562
|
}
|
|
521
563
|
}
|
|
522
564
|
}
|
|
523
565
|
}
|
|
524
566
|
|
|
525
567
|
// Start following from the root exposure
|
|
526
|
-
|
|
568
|
+
// Initial path contains the root entity itself
|
|
569
|
+
const rootKey = createDomainKey(parentExposure.entity)
|
|
570
|
+
follow(parentExposure.entity, parentExposure.key, 0, [rootKey])
|
|
527
571
|
}
|
|
528
572
|
|
|
529
573
|
/**
|
|
530
574
|
* Removes an exposed entity from the API model.
|
|
531
|
-
*
|
|
575
|
+
* This also removes any nested exposed entities that are children of the removed entity.
|
|
576
|
+
*
|
|
577
|
+
* @param key The key of the exposed entity to remove.
|
|
532
578
|
*/
|
|
533
|
-
|
|
534
|
-
const
|
|
535
|
-
if (
|
|
536
|
-
|
|
579
|
+
removeExposedEntity(key: string): void {
|
|
580
|
+
const index = this.exposes.findIndex((e) => e.key === key)
|
|
581
|
+
if (index < 0) {
|
|
582
|
+
throw new Error(`Exposed entity with key "${key}" not found.`)
|
|
537
583
|
}
|
|
538
|
-
this.removeWithChildren(
|
|
539
|
-
this.notifyChange()
|
|
584
|
+
this.removeWithChildren(key)
|
|
540
585
|
}
|
|
541
586
|
|
|
542
587
|
private removeWithChildren(key: string): void {
|
|
@@ -573,11 +618,11 @@ export class ApiModel extends DependentModel {
|
|
|
573
618
|
}
|
|
574
619
|
|
|
575
620
|
/**
|
|
576
|
-
* Clears the API model for a new
|
|
621
|
+
* Clears the API model for a new data domain change.
|
|
577
622
|
* This method resets the dependencies, exposes, user,
|
|
578
623
|
* authentication, authorization, and session properties.
|
|
579
624
|
*/
|
|
580
|
-
|
|
625
|
+
cleanForDomainChange(): void {
|
|
581
626
|
this.dependencies.clear()
|
|
582
627
|
this.dependencyList = []
|
|
583
628
|
this.exposes = []
|
|
@@ -606,7 +651,7 @@ export class ApiModel extends DependentModel {
|
|
|
606
651
|
if (!domain.info.version) {
|
|
607
652
|
throw new Error(`Cannot attach DataDomain without a version. Please set the version in the domain info.`)
|
|
608
653
|
}
|
|
609
|
-
this.
|
|
654
|
+
this.cleanForDomainChange()
|
|
610
655
|
this.dependencies.set(domain.key, domain)
|
|
611
656
|
this.dependencyList = [{ key: domain.key, version: domain.info.version }]
|
|
612
657
|
this.notifyChange()
|
|
@@ -16,6 +16,15 @@ import type {
|
|
|
16
16
|
/**
|
|
17
17
|
* A class that specializes in representing an exposed Data Entity within an API Model.
|
|
18
18
|
*
|
|
19
|
+
* ## Design Note
|
|
20
|
+
* This class enforces strict path constraints (e.g., single-segment collection paths like `/users`,
|
|
21
|
+
* two-segment resource paths like `/users/{id}`).
|
|
22
|
+
* This is an intentional design choice to support a UI paradigm for non-technical users, ensuring that
|
|
23
|
+
* path segments are configured individually at each level of the exposure hierarchy.
|
|
24
|
+
*
|
|
25
|
+
* Flexibility is achieved by chaining exposed entities (parent/child relationships), where the final
|
|
26
|
+
* absolute path is composed of all ancestral paths. See `getAbsoluteResourcePath()` and `getAbsoluteCollectionPath()`.
|
|
27
|
+
*
|
|
19
28
|
* @fires change - Emitted when the exposed entity has changed.
|
|
20
29
|
*/
|
|
21
30
|
export class ExposedEntity extends EventTarget {
|
|
@@ -250,6 +259,17 @@ export class ExposedEntity extends EventTarget {
|
|
|
250
259
|
throw new Error(`Collection path must contain exactly one segment. Received: "${path}"`)
|
|
251
260
|
}
|
|
252
261
|
const normalizedCollection = `/${segments[0]}`
|
|
262
|
+
|
|
263
|
+
// Check for collision if this is a root entity
|
|
264
|
+
if (this.isRoot) {
|
|
265
|
+
const collision = this.api.exposes.find(
|
|
266
|
+
(e) => e.isRoot && e.key !== this.key && e.collectionPath === normalizedCollection
|
|
267
|
+
)
|
|
268
|
+
if (collision) {
|
|
269
|
+
throw new Error(`Collection path "${normalizedCollection}" is already in use by another root entity.`)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
253
273
|
// Preserve current parameter name if present, otherwise default to {id}
|
|
254
274
|
let param = '{id}'
|
|
255
275
|
if (this.resourcePath) {
|
|
@@ -309,6 +329,14 @@ export class ExposedEntity extends EventTarget {
|
|
|
309
329
|
`Resource path must contain exactly two segments when no collection is present. Received: "${cleaned}"`
|
|
310
330
|
)
|
|
311
331
|
}
|
|
332
|
+
// Check for collision if this is a root entity (singleton case)
|
|
333
|
+
if (this.isRoot) {
|
|
334
|
+
const collision = this.api.exposes.find((e) => e.isRoot && e.key !== this.key && e.resourcePath === cleaned)
|
|
335
|
+
if (collision) {
|
|
336
|
+
throw new Error(`Resource path "${cleaned}" is already in use by another root entity.`)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
312
340
|
if (this.resourcePath !== cleaned) {
|
|
313
341
|
this.resourcePath = `/${segments[0]}/${segments[1]}`
|
|
314
342
|
}
|
package/src/models/README.md
CHANGED
|
@@ -7,14 +7,15 @@
|
|
|
7
7
|
Data models should only contain the data related to the functionality, and not view.
|
|
8
8
|
Each application should keep separate metadata related to the view configuration.
|
|
9
9
|
|
|
10
|
-
### Translatable from a YAML format
|
|
11
|
-
|
|
12
|
-
We aim to be interoperable with AMF so when designing a data model first design a YAML example with the data and then create a type definition. Additionally, you need to design an AML dialect for the data.
|
|
13
|
-
|
|
14
10
|
### The "kind" as the object type identifier
|
|
15
11
|
|
|
16
12
|
Each data object that can exist by itself or in multiple contexts' should contain the `kind` property that uniquely identifies the type. For example, the `HttpProject` class has the `Core#HttpProject`. These kinds are used by other libraries to identify which data types they are given as the input.
|
|
13
|
+
Kinds are defined in the `src/models/kinds.ts` file.
|
|
17
14
|
|
|
18
15
|
### Validation
|
|
19
16
|
|
|
20
17
|
THe data models should contain validators that produce the validation report. This ensures the support libraries can perform a validation of the data model they are receiving as an input to keep integrity of the data.
|
|
18
|
+
|
|
19
|
+
## The "store" models
|
|
20
|
+
|
|
21
|
+
All models under the `src/models/store` directory are considered "API" related models. For example, the `File` model is used to describe the file object in the API, but it is not used as any application's core data model.
|