@apso/cli 0.1.0 → 0.1.4
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/dist/commands/server/scaffold.d.ts +1 -1
- package/dist/commands/server/scaffold.js +5 -6
- package/dist/lib/apsorc-parser.d.ts +27 -1
- package/dist/lib/apsorc-parser.js +97 -3
- package/dist/lib/controller.d.ts +8 -0
- package/dist/lib/controller.js +24 -9
- package/dist/lib/dto.d.ts +13 -7
- package/dist/lib/dto.js +33 -25
- package/dist/lib/entity.d.ts +9 -0
- package/dist/lib/entity.js +13 -3
- package/dist/lib/index-module.d.ts +11 -0
- package/dist/lib/index-module.js +11 -0
- package/dist/lib/module.d.ts +13 -1
- package/dist/lib/module.js +26 -10
- package/dist/lib/service.d.ts +9 -1
- package/dist/lib/service.js +22 -7
- package/dist/lib/templates/rest/service-rest-spec.eta +38 -0
- package/dist/lib/utils/relationships/parse.d.ts +45 -1
- package/dist/lib/utils/relationships/parse.js +226 -55
- package/dist/lib/utils/relationships/parse.spec.d.ts +1 -0
- package/dist/lib/utils/relationships/parse.spec.js +218 -0
- package/dist/tests/utils/relationship-deduplication.spec.d.ts +1 -0
- package/dist/tests/utils/relationship-deduplication.spec.js +97 -0
- package/oclif.manifest.json +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const globals_1 = require("@jest/globals");
|
|
4
|
+
const parse_1 = require("./parse");
|
|
5
|
+
(0, globals_1.describe)('parseRelationships', () => {
|
|
6
|
+
// Mock console.warn to prevent test output clutter
|
|
7
|
+
(0, globals_1.beforeEach)(() => {
|
|
8
|
+
globals_1.jest.spyOn(console, 'warn').mockImplementation(() => { });
|
|
9
|
+
});
|
|
10
|
+
(0, globals_1.afterEach)(() => {
|
|
11
|
+
globals_1.jest.restoreAllMocks();
|
|
12
|
+
});
|
|
13
|
+
(0, globals_1.describe)('ManyToMany Relationships', () => {
|
|
14
|
+
(0, globals_1.it)('should parse a single bidirectional ManyToMany definition', () => {
|
|
15
|
+
const relationships = [
|
|
16
|
+
{
|
|
17
|
+
from: 'EntityA',
|
|
18
|
+
to: 'EntityB',
|
|
19
|
+
type: 'ManyToMany',
|
|
20
|
+
// eslint-disable-next-line camelcase
|
|
21
|
+
bi_directional: true,
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
const expected = {
|
|
25
|
+
EntityA: [
|
|
26
|
+
{
|
|
27
|
+
name: 'EntityB',
|
|
28
|
+
type: 'ManyToMany',
|
|
29
|
+
referenceName: 'entitybs',
|
|
30
|
+
join: true,
|
|
31
|
+
biDirectional: true,
|
|
32
|
+
joinTableName: undefined,
|
|
33
|
+
joinColumnName: undefined,
|
|
34
|
+
inverseJoinColumnName: undefined,
|
|
35
|
+
inverseReferenceName: 'entityas', // Simplified default
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
EntityB: [
|
|
39
|
+
{
|
|
40
|
+
name: 'EntityA',
|
|
41
|
+
type: 'ManyToMany',
|
|
42
|
+
referenceName: 'entityas',
|
|
43
|
+
join: false,
|
|
44
|
+
biDirectional: true,
|
|
45
|
+
joinTableName: undefined,
|
|
46
|
+
joinColumnName: undefined,
|
|
47
|
+
inverseJoinColumnName: undefined,
|
|
48
|
+
inverseReferenceName: 'entitybs', // Simplified default
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
const result = (0, parse_1.parseRelationships)(relationships);
|
|
53
|
+
(0, globals_1.expect)(result).toEqual(expected);
|
|
54
|
+
});
|
|
55
|
+
(0, globals_1.it)('should parse a single bidirectional ManyToMany with to_name', () => {
|
|
56
|
+
const relationships = [
|
|
57
|
+
{
|
|
58
|
+
from: 'User',
|
|
59
|
+
to: 'Group',
|
|
60
|
+
type: 'ManyToMany',
|
|
61
|
+
// eslint-disable-next-line camelcase
|
|
62
|
+
bi_directional: true,
|
|
63
|
+
// eslint-disable-next-line camelcase
|
|
64
|
+
to_name: 'userGroups',
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
const expected = {
|
|
68
|
+
User: [
|
|
69
|
+
{
|
|
70
|
+
name: 'Group',
|
|
71
|
+
type: 'ManyToMany',
|
|
72
|
+
referenceName: 'userGroups',
|
|
73
|
+
join: true,
|
|
74
|
+
biDirectional: true,
|
|
75
|
+
joinTableName: undefined,
|
|
76
|
+
joinColumnName: undefined,
|
|
77
|
+
inverseJoinColumnName: undefined,
|
|
78
|
+
inverseReferenceName: 'users', // Simplified default
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
Group: [
|
|
82
|
+
{
|
|
83
|
+
name: 'User',
|
|
84
|
+
type: 'ManyToMany',
|
|
85
|
+
referenceName: 'users',
|
|
86
|
+
join: false,
|
|
87
|
+
biDirectional: true,
|
|
88
|
+
joinTableName: undefined,
|
|
89
|
+
joinColumnName: undefined,
|
|
90
|
+
inverseJoinColumnName: undefined,
|
|
91
|
+
inverseReferenceName: 'userGroups', // Expect preserved camelCase
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
const result = (0, parse_1.parseRelationships)(relationships);
|
|
96
|
+
(0, globals_1.expect)(result).toEqual(expected);
|
|
97
|
+
});
|
|
98
|
+
(0, globals_1.it)('should parse a unidirectional ManyToMany definition', () => {
|
|
99
|
+
const relationships = [
|
|
100
|
+
{
|
|
101
|
+
from: 'Post',
|
|
102
|
+
to: 'Tag',
|
|
103
|
+
type: 'ManyToMany',
|
|
104
|
+
// bi_directional is false or omitted
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
const expected = {
|
|
108
|
+
Post: [
|
|
109
|
+
{
|
|
110
|
+
name: 'Tag',
|
|
111
|
+
type: 'ManyToMany',
|
|
112
|
+
referenceName: 'tags',
|
|
113
|
+
join: true,
|
|
114
|
+
biDirectional: false,
|
|
115
|
+
joinTableName: undefined,
|
|
116
|
+
joinColumnName: undefined,
|
|
117
|
+
inverseJoinColumnName: undefined,
|
|
118
|
+
inverseReferenceName: undefined, // Correct: No inverse name for unidirectional
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
// No entry expected for Tag
|
|
122
|
+
};
|
|
123
|
+
const result = (0, parse_1.parseRelationships)(relationships);
|
|
124
|
+
(0, globals_1.expect)(result).toEqual(expected);
|
|
125
|
+
});
|
|
126
|
+
(0, globals_1.it)('should handle join table details correctly', () => {
|
|
127
|
+
const relationships = [
|
|
128
|
+
{
|
|
129
|
+
from: 'Student',
|
|
130
|
+
to: 'Course',
|
|
131
|
+
type: 'ManyToMany',
|
|
132
|
+
// eslint-disable-next-line camelcase
|
|
133
|
+
bi_directional: true,
|
|
134
|
+
// UNUSED: eslint-disable-next-line camelcase
|
|
135
|
+
joinTableName: 'enrollments',
|
|
136
|
+
// UNUSED: eslint-disable-next-line camelcase
|
|
137
|
+
joinColumnName: 'student_id',
|
|
138
|
+
// UNUSED: eslint-disable-next-line camelcase
|
|
139
|
+
inverseJoinColumnName: 'course_id',
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
const expected = {
|
|
143
|
+
Student: [
|
|
144
|
+
{
|
|
145
|
+
name: 'Course',
|
|
146
|
+
type: 'ManyToMany',
|
|
147
|
+
referenceName: 'courses',
|
|
148
|
+
join: true,
|
|
149
|
+
biDirectional: true,
|
|
150
|
+
joinTableName: 'enrollments',
|
|
151
|
+
joinColumnName: 'student_id',
|
|
152
|
+
inverseJoinColumnName: 'course_id',
|
|
153
|
+
inverseReferenceName: 'students', // Simplified default
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
Course: [
|
|
157
|
+
{
|
|
158
|
+
name: 'Student',
|
|
159
|
+
type: 'ManyToMany',
|
|
160
|
+
referenceName: 'students',
|
|
161
|
+
join: false,
|
|
162
|
+
biDirectional: true,
|
|
163
|
+
joinTableName: undefined,
|
|
164
|
+
joinColumnName: undefined,
|
|
165
|
+
inverseJoinColumnName: undefined,
|
|
166
|
+
inverseReferenceName: 'courses', // Simplified default
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
};
|
|
170
|
+
const result = (0, parse_1.parseRelationships)(relationships);
|
|
171
|
+
(0, globals_1.expect)(result).toEqual(expected);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
(0, globals_1.describe)('Mixed Relationship Types', () => {
|
|
175
|
+
(0, globals_1.it)('should correctly parse ManyToMany alongside other types', () => {
|
|
176
|
+
const relationships = [
|
|
177
|
+
{
|
|
178
|
+
from: 'User',
|
|
179
|
+
to: 'Profile',
|
|
180
|
+
type: 'OneToOne',
|
|
181
|
+
// eslint-disable-next-line camelcase
|
|
182
|
+
bi_directional: true,
|
|
183
|
+
// eslint-disable-next-line camelcase
|
|
184
|
+
to_name: 'userProfile'
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
from: 'User',
|
|
188
|
+
to: 'Post',
|
|
189
|
+
type: 'OneToMany',
|
|
190
|
+
// eslint-disable-next-line camelcase
|
|
191
|
+
bi_directional: true // Implies ManyToOne on Post
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
from: 'Role',
|
|
195
|
+
to: 'User',
|
|
196
|
+
type: 'ManyToMany',
|
|
197
|
+
// eslint-disable-next-line camelcase
|
|
198
|
+
bi_directional: true
|
|
199
|
+
}
|
|
200
|
+
];
|
|
201
|
+
const result = (0, parse_1.parseRelationships)(relationships);
|
|
202
|
+
// Check User relationships
|
|
203
|
+
(0, globals_1.expect)(result.User).toHaveLength(3);
|
|
204
|
+
(0, globals_1.expect)(result.User).toContainEqual(globals_1.expect.objectContaining({ name: 'Profile', type: 'OneToOne', referenceName: 'userProfile', biDirectional: true, join: false })); // Expect preserved camelCase
|
|
205
|
+
(0, globals_1.expect)(result.User).toContainEqual(globals_1.expect.objectContaining({ name: 'Post', type: 'OneToMany', referenceName: 'posts', biDirectional: true })); // Simplified default
|
|
206
|
+
(0, globals_1.expect)(result.User).toContainEqual(globals_1.expect.objectContaining({ name: 'Role', type: 'ManyToMany', referenceName: 'roles', biDirectional: true, join: false })); // Simplified default
|
|
207
|
+
// Check Profile relationship
|
|
208
|
+
(0, globals_1.expect)(result.Profile).toHaveLength(1);
|
|
209
|
+
(0, globals_1.expect)(result.Profile).toContainEqual(globals_1.expect.objectContaining({ name: 'User', type: 'OneToOne', referenceName: 'user', biDirectional: true, join: true })); // Simplified default singular
|
|
210
|
+
// Check Post relationship
|
|
211
|
+
(0, globals_1.expect)(result.Post).toHaveLength(1);
|
|
212
|
+
(0, globals_1.expect)(result.Post).toContainEqual(globals_1.expect.objectContaining({ name: 'User', type: 'ManyToOne', referenceName: 'user', biDirectional: true })); // Simplified default singular
|
|
213
|
+
// Check Role relationship
|
|
214
|
+
(0, globals_1.expect)(result.Role).toHaveLength(1);
|
|
215
|
+
(0, globals_1.expect)(result.Role).toContainEqual(globals_1.expect.objectContaining({ name: 'User', type: 'ManyToMany', referenceName: 'users', biDirectional: true, join: true })); // Simplified default
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const globals_1 = require("@jest/globals");
|
|
4
|
+
const parse_1 = require("../../lib/utils/relationships/parse");
|
|
5
|
+
(0, globals_1.describe)("getRelationshipForTemplate deduplication", () => {
|
|
6
|
+
(0, globals_1.test)("should deduplicate ManyToMany relationships", () => {
|
|
7
|
+
// Mock entity name
|
|
8
|
+
const entityName = "CustomerCard";
|
|
9
|
+
// Mock relationships - simulating bidirectional ManyToMany where
|
|
10
|
+
// both sides are added to the relationships array for the same entity
|
|
11
|
+
const mockRelationships = [
|
|
12
|
+
// First ManyToMany relationship (non-join side)
|
|
13
|
+
{
|
|
14
|
+
name: "Subscription",
|
|
15
|
+
type: "ManyToMany",
|
|
16
|
+
referenceName: "subscriptions",
|
|
17
|
+
biDirectional: true,
|
|
18
|
+
join: false,
|
|
19
|
+
inverseReferenceName: "customercards"
|
|
20
|
+
},
|
|
21
|
+
// Duplicate relationship (join side) - similar to what happens in bidirectional ManyToMany
|
|
22
|
+
{
|
|
23
|
+
name: "Subscription",
|
|
24
|
+
type: "ManyToMany",
|
|
25
|
+
referenceName: "subscriptions",
|
|
26
|
+
biDirectional: true,
|
|
27
|
+
join: true,
|
|
28
|
+
inverseReferenceName: "customercards"
|
|
29
|
+
}
|
|
30
|
+
];
|
|
31
|
+
// Run the function
|
|
32
|
+
const result = (0, parse_1.getRelationshipForTemplate)(entityName, mockRelationships);
|
|
33
|
+
// Assertions
|
|
34
|
+
// 1. Should only have one relationship
|
|
35
|
+
(0, globals_1.expect)(result.length).toBe(1);
|
|
36
|
+
// 2. The remaining relationship should be the one with join=true
|
|
37
|
+
(0, globals_1.expect)(result[0].name).toBe("Subscription");
|
|
38
|
+
(0, globals_1.expect)(result[0].joinTable).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
(0, globals_1.test)("should keep first relationship when reference names differ", () => {
|
|
41
|
+
// Mock entity name
|
|
42
|
+
const entityName = "User";
|
|
43
|
+
// Create mocked relationships with same target but different reference names
|
|
44
|
+
const mockRelationships = [
|
|
45
|
+
{
|
|
46
|
+
name: "Workspace",
|
|
47
|
+
type: "ManyToMany",
|
|
48
|
+
referenceName: "workspaces",
|
|
49
|
+
biDirectional: true,
|
|
50
|
+
join: true
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "Workspace",
|
|
54
|
+
type: "ManyToMany",
|
|
55
|
+
referenceName: "ownedWorkspaces",
|
|
56
|
+
biDirectional: true,
|
|
57
|
+
join: true
|
|
58
|
+
}
|
|
59
|
+
];
|
|
60
|
+
// Run the function
|
|
61
|
+
const result = (0, parse_1.getRelationshipForTemplate)(entityName, mockRelationships);
|
|
62
|
+
// Assertions
|
|
63
|
+
// Current behavior: Only one relationship is kept during name-based deduplication (second phase)
|
|
64
|
+
(0, globals_1.expect)(result.length).toBe(1);
|
|
65
|
+
// The first one is kept in the Set processing
|
|
66
|
+
(0, globals_1.expect)(result[0].relationshipName).toBe("workspaces");
|
|
67
|
+
});
|
|
68
|
+
(0, globals_1.test)("should prioritize owner side (join=true) when deduplicating", () => {
|
|
69
|
+
// Scenario based on real issue: two ManyToMany relationships to same entity
|
|
70
|
+
const entityName = "Tag";
|
|
71
|
+
// Create relationships where one has join=true and one has join=false (non-owning side)
|
|
72
|
+
const mockRelationships = [
|
|
73
|
+
{
|
|
74
|
+
name: "WorkspaceService",
|
|
75
|
+
type: "ManyToMany",
|
|
76
|
+
biDirectional: true,
|
|
77
|
+
join: false,
|
|
78
|
+
referenceName: "workspaceservices"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "WorkspaceService",
|
|
82
|
+
type: "ManyToMany",
|
|
83
|
+
biDirectional: true,
|
|
84
|
+
join: true,
|
|
85
|
+
referenceName: "workspaceservices"
|
|
86
|
+
}
|
|
87
|
+
];
|
|
88
|
+
// Run the function
|
|
89
|
+
const result = (0, parse_1.getRelationshipForTemplate)(entityName, mockRelationships);
|
|
90
|
+
// Assertions
|
|
91
|
+
// Should only have one relationship
|
|
92
|
+
(0, globals_1.expect)(result.length).toBe(1);
|
|
93
|
+
// Should be the one with join=true
|
|
94
|
+
(0, globals_1.expect)(result[0].name).toBe("WorkspaceService");
|
|
95
|
+
(0, globals_1.expect)(result[0].joinTable).toBe(true); // joinTable corresponds to join
|
|
96
|
+
});
|
|
97
|
+
});
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apso/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Apso CLI",
|
|
5
5
|
"author": "Apso by Mavric - @mavric",
|
|
6
6
|
"bin": {
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"ts-jest": "^29.1.0",
|
|
57
57
|
"ts-node": "^10.9.1",
|
|
58
58
|
"tslib": "^2.5.0",
|
|
59
|
+
"typedoc": "^0.28.3",
|
|
59
60
|
"typescript": "^5"
|
|
60
61
|
},
|
|
61
62
|
"oclif": {
|
|
@@ -86,6 +87,7 @@
|
|
|
86
87
|
"test": "jest",
|
|
87
88
|
"test:watch": "jest --watch",
|
|
88
89
|
"test:cov": "jest --coverage",
|
|
90
|
+
"docs:generate": "typedoc src/lib/index.ts --out docs",
|
|
89
91
|
"version": "oclif readme && git add README.md"
|
|
90
92
|
},
|
|
91
93
|
"engines": {
|