@apso/cli 0.4.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +796 -6
- package/dist/commands/server/new.js +1 -1
- package/dist/commands/server/scaffold.js +20 -9
- package/dist/lib/apsorc-parser.d.ts +3 -0
- package/dist/lib/apsorc-parser.js +11 -6
- package/dist/lib/controller.js +3 -1
- package/dist/lib/dto.js +9 -9
- package/dist/lib/entity.js +6 -5
- package/dist/lib/guards.d.ts +47 -0
- package/dist/lib/guards.js +176 -0
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.js +5 -1
- package/dist/lib/module.js +3 -1
- package/dist/lib/templates/guards/auth.guard.eta +228 -0
- package/dist/lib/templates/guards/guards.module.eta +114 -0
- package/dist/lib/templates/guards/index.eta +22 -0
- package/dist/lib/templates/guards/scope.guard.eta +327 -0
- package/dist/lib/types/auth.d.ts +203 -0
- package/dist/lib/types/auth.js +60 -0
- package/dist/lib/types/entity.d.ts +36 -1
- package/dist/lib/types/index.d.ts +2 -1
- package/dist/lib/types/index.js +8 -0
- package/dist/lib/types/relationship.d.ts +1 -1
- package/dist/lib/utils/casing.js +5 -5
- package/dist/lib/utils/field.js +2 -1
- package/dist/lib/utils/file-system.js +1 -1
- package/dist/lib/utils/relationships/parse-v1.js +2 -1
- package/dist/lib/utils/relationships/parse.js +117 -32
- package/dist/lib/utils/relationships/parse.spec.js +168 -141
- package/dist/test-nested-relationships.js +10 -10
- package/dist/tests/templates/entities/types/array-template.spec.js +45 -45
- package/dist/tests/templates/entities/types/bigint-template.spec.js +43 -43
- package/dist/tests/templates/entities/types/boolean-template.spec.js +44 -44
- package/dist/tests/templates/entities/types/bytea-template.spec.js +35 -35
- package/dist/tests/templates/entities/types/char-template.spec.js +35 -35
- package/dist/tests/templates/entities/types/date-template.spec.js +29 -29
- package/dist/tests/templates/entities/types/decimal-field-validation.spec.js +46 -46
- package/dist/tests/templates/entities/types/decimal-template.spec.js +46 -46
- package/dist/tests/templates/entities/types/double-template.spec.js +37 -34
- package/dist/tests/templates/entities/types/enum-template.spec.js +42 -42
- package/dist/tests/templates/entities/types/float-template.spec.js +37 -34
- package/dist/tests/templates/entities/types/geometry-template.spec.js +31 -31
- package/dist/tests/templates/entities/types/inet-template.spec.js +36 -36
- package/dist/tests/templates/entities/types/int4range-template.spec.js +45 -33
- package/dist/tests/templates/entities/types/integer-template.spec.js +40 -40
- package/dist/tests/templates/entities/types/interval-template.spec.js +35 -35
- package/dist/tests/templates/entities/types/json-plain-template.spec.js +27 -27
- package/dist/tests/templates/entities/types/json-template.spec.js +19 -19
- package/dist/tests/templates/entities/types/jsonb-template.spec.js +19 -19
- package/dist/tests/templates/entities/types/money-template.spec.js +35 -35
- package/dist/tests/templates/entities/types/numeric-template.spec.js +44 -44
- package/dist/tests/templates/entities/types/point-template.spec.js +37 -31
- package/dist/tests/templates/entities/types/polygon-template.spec.js +43 -31
- package/dist/tests/templates/entities/types/real-template.spec.js +34 -34
- package/dist/tests/templates/entities/types/serial-template.spec.js +62 -62
- package/dist/tests/templates/entities/types/smallint-template.spec.js +43 -43
- package/dist/tests/templates/entities/types/string-template.spec.js +59 -59
- package/dist/tests/templates/entities/types/text-template.spec.js +37 -37
- package/dist/tests/templates/entities/types/time-template.spec.js +35 -35
- package/dist/tests/templates/entities/types/timestamp-template.spec.js +39 -39
- package/dist/tests/templates/entities/types/timetz-template.spec.js +35 -35
- package/dist/tests/templates/entities/types/tsvector-template.spec.js +28 -28
- package/dist/tests/templates/entities/types/uuid-template.spec.js +43 -43
- package/dist/tests/templates/entities/types/varchar-template.spec.js +59 -56
- package/dist/tests/templates/entities/types/xml-template.spec.js +28 -28
- package/dist/tests/templates/entities/utils/template-test-utils.d.ts +1 -1
- package/dist/tests/templates/entities/utils/template-test-utils.js +3 -3
- package/dist/tests/templates/entities/uuid-entity-template.spec.js +138 -138
- package/dist/tests/utils/relationship-deduplication.spec.js +11 -11
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
|
@@ -72,7 +72,7 @@ class New extends base_command_1.default {
|
|
|
72
72
|
let projectName = flags.name;
|
|
73
73
|
let apiType = (_a = flags.type) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
74
74
|
if (!projectName) {
|
|
75
|
-
projectName =
|
|
75
|
+
projectName = "";
|
|
76
76
|
}
|
|
77
77
|
if (!apiType) {
|
|
78
78
|
apiType = "rest";
|
|
@@ -10,7 +10,7 @@ const perf_hooks_1 = require("perf_hooks");
|
|
|
10
10
|
const Eta = tslib_1.__importStar(require("eta"));
|
|
11
11
|
Eta.configure({
|
|
12
12
|
views: path.join(__dirname, "../../lib/templates"),
|
|
13
|
-
cache: false
|
|
13
|
+
cache: false,
|
|
14
14
|
});
|
|
15
15
|
class Scaffold extends base_command_1.default {
|
|
16
16
|
async scaffoldServer(options) {
|
|
@@ -25,7 +25,7 @@ class Scaffold extends base_command_1.default {
|
|
|
25
25
|
entity,
|
|
26
26
|
relationships: entityRelationships,
|
|
27
27
|
apiType,
|
|
28
|
-
allEntities
|
|
28
|
+
allEntities,
|
|
29
29
|
});
|
|
30
30
|
switch (apiType) {
|
|
31
31
|
case apsorc_parser_1.ApiType.Graphql:
|
|
@@ -45,18 +45,25 @@ class Scaffold extends base_command_1.default {
|
|
|
45
45
|
const filePath = path.join(dir, entity.name);
|
|
46
46
|
const entityRelationships = relationshipMap[entity.name] || [];
|
|
47
47
|
const tDto = perf_hooks_1.performance.now();
|
|
48
|
-
await (0, lib_1.createDto)(filePath, entity, entityRelationships, {
|
|
48
|
+
await (0, lib_1.createDto)(filePath, entity, entityRelationships, {
|
|
49
|
+
apiType,
|
|
50
|
+
allEntities,
|
|
51
|
+
});
|
|
49
52
|
if (process.env.DEBUG) {
|
|
50
53
|
console.log(`[timing] createDto for ${entity.name}: ${(perf_hooks_1.performance.now() - tDto).toFixed(2)}ms`);
|
|
51
54
|
const used = process.memoryUsage();
|
|
52
|
-
console.log(`[mem] heapUsed after createDto for ${entity.name}: ${(used.heapUsed /
|
|
55
|
+
console.log(`[mem] heapUsed after createDto for ${entity.name}: ${(used.heapUsed /
|
|
56
|
+
1024 /
|
|
57
|
+
1024).toFixed(2)} MB`);
|
|
53
58
|
}
|
|
54
59
|
const tService = perf_hooks_1.performance.now();
|
|
55
60
|
await (0, lib_1.createService)(filePath, entity, relationshipMap);
|
|
56
61
|
if (process.env.DEBUG) {
|
|
57
62
|
console.log(`[timing] createService for ${entity.name}: ${(perf_hooks_1.performance.now() - tService).toFixed(2)}ms`);
|
|
58
63
|
const used = process.memoryUsage();
|
|
59
|
-
console.log(`[mem] heapUsed after createService for ${entity.name}: ${(used.heapUsed /
|
|
64
|
+
console.log(`[mem] heapUsed after createService for ${entity.name}: ${(used.heapUsed /
|
|
65
|
+
1024 /
|
|
66
|
+
1024).toFixed(2)} MB`);
|
|
60
67
|
}
|
|
61
68
|
const tController = perf_hooks_1.performance.now();
|
|
62
69
|
await (0, lib_1.createController)(filePath, entity, relationshipMap);
|
|
@@ -71,7 +78,7 @@ class Scaffold extends base_command_1.default {
|
|
|
71
78
|
}
|
|
72
79
|
async run() {
|
|
73
80
|
const totalBuildStart = perf_hooks_1.performance.now();
|
|
74
|
-
const { rootFolder, entities, relationshipMap, apiType } = (0, lib_1.parseApsorc)();
|
|
81
|
+
const { rootFolder, entities, relationshipMap, apiType, auth } = (0, lib_1.parseApsorc)();
|
|
75
82
|
const rootPath = path.join(process.cwd(), rootFolder);
|
|
76
83
|
const autogenPath = path.join(rootPath, "autogen");
|
|
77
84
|
const lowerCaseApiType = apiType.toLowerCase();
|
|
@@ -83,19 +90,23 @@ class Scaffold extends base_command_1.default {
|
|
|
83
90
|
entity,
|
|
84
91
|
relationshipMap,
|
|
85
92
|
apiType: lowerCaseApiType,
|
|
86
|
-
allEntities: entities
|
|
93
|
+
allEntities: entities,
|
|
87
94
|
});
|
|
88
95
|
if (process.env.DEBUG) {
|
|
89
96
|
const used = process.memoryUsage();
|
|
90
|
-
console.log(`[mem] heapUsed after ${entity.name}: ${(used.heapUsed /
|
|
97
|
+
console.log(`[mem] heapUsed after ${entity.name}: ${(used.heapUsed /
|
|
98
|
+
1024 /
|
|
99
|
+
1024).toFixed(2)} MB`);
|
|
91
100
|
}
|
|
92
101
|
}));
|
|
102
|
+
// Generate guards (auth and/or scope) based on .apsorc configuration
|
|
103
|
+
await (0, lib_1.createGuards)(rootPath, entities, auth);
|
|
93
104
|
await (0, lib_1.createIndexAppModule)(autogenPath, entities, lowerCaseApiType);
|
|
94
105
|
const totalBuildTime = perf_hooks_1.performance.now() - totalBuildStart;
|
|
95
106
|
console.log(`[apso] Finished building all entities in ${totalBuildTime.toFixed(2)} ms`);
|
|
96
107
|
const formatStart = perf_hooks_1.performance.now();
|
|
97
108
|
console.log("[apso] Formatting files...");
|
|
98
|
-
await this.runNpmCommand(["run", "format", "src/autogen/**/*.ts"], true);
|
|
109
|
+
await this.runNpmCommand(["run", "format", "src/autogen/**/*.ts", "src/guards/**/*.ts"], true);
|
|
99
110
|
const formatTime = perf_hooks_1.performance.now() - formatStart;
|
|
100
111
|
console.log(`[apso] Finished formatting in ${formatTime.toFixed(2)} ms`);
|
|
101
112
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Entity } from "./types/entity";
|
|
2
2
|
import { ApsorcRelationship, RelationshipMap } from "./types/relationship";
|
|
3
|
+
import { AuthConfig } from "./types/auth";
|
|
3
4
|
export declare enum ApiType {
|
|
4
5
|
Graphql = "graphql",
|
|
5
6
|
Rest = "rest"
|
|
@@ -10,6 +11,7 @@ export type ApsorcType = {
|
|
|
10
11
|
entities: Entity[];
|
|
11
12
|
apiType: ApiType;
|
|
12
13
|
relationships: ApsorcRelationship[];
|
|
14
|
+
auth?: AuthConfig;
|
|
13
15
|
};
|
|
14
16
|
type ParsedApsorcData = {
|
|
15
17
|
entities: Entity[];
|
|
@@ -20,6 +22,7 @@ type ParsedApsorc = {
|
|
|
20
22
|
apiType: string;
|
|
21
23
|
entities: Entity[];
|
|
22
24
|
relationshipMap: RelationshipMap;
|
|
25
|
+
auth?: AuthConfig;
|
|
23
26
|
};
|
|
24
27
|
export declare const parseApsorcV1: (apsorc: ApsorcType) => ParsedApsorcData;
|
|
25
28
|
export declare const parseApsorcV2: (apsorc: ApsorcType) => ParsedApsorcData;
|
|
@@ -34,12 +34,14 @@ const parseRc = () => {
|
|
|
34
34
|
const version = apsoConfig.version || 1;
|
|
35
35
|
const entities = apsoConfig.entities || [];
|
|
36
36
|
const relationships = apsoConfig.relationships || [];
|
|
37
|
+
const auth = apsoConfig.auth;
|
|
37
38
|
return {
|
|
38
39
|
rootFolder,
|
|
39
40
|
apiType,
|
|
40
41
|
version,
|
|
41
42
|
entities,
|
|
42
43
|
relationships,
|
|
44
|
+
auth,
|
|
43
45
|
};
|
|
44
46
|
};
|
|
45
47
|
const parseApsorc = () => {
|
|
@@ -57,6 +59,7 @@ const parseApsorc = () => {
|
|
|
57
59
|
const result = {
|
|
58
60
|
rootFolder: apsoConfig.rootFolder,
|
|
59
61
|
apiType: apsoConfig.apiType,
|
|
62
|
+
auth: apsoConfig.auth,
|
|
60
63
|
...(0, exports.parseApsorcV1)(apsoConfig),
|
|
61
64
|
};
|
|
62
65
|
if (debug) {
|
|
@@ -71,6 +74,7 @@ const parseApsorc = () => {
|
|
|
71
74
|
const result = {
|
|
72
75
|
rootFolder: apsoConfig.rootFolder,
|
|
73
76
|
apiType: apsoConfig.apiType,
|
|
77
|
+
auth: apsoConfig.auth,
|
|
74
78
|
...(() => {
|
|
75
79
|
const relStart = perf_hooks_1.performance.now();
|
|
76
80
|
const parsed = (0, exports.parseApsorcV2)(apsoConfig);
|
|
@@ -128,11 +132,12 @@ const loadConfig = (appName = "apso", defaults = {}) => {
|
|
|
128
132
|
const configData = (0, rc_1.default)(appName, defaults);
|
|
129
133
|
const rootFolder = configData.rootFolder || "src"; // Get rootFolder early
|
|
130
134
|
// Check if rc found and loaded the config
|
|
131
|
-
// rc returns an object with default keys if nothing is found,
|
|
135
|
+
// rc returns an object with default keys if nothing is found,
|
|
132
136
|
// so check for more than just default keys or if configData.config exists
|
|
133
137
|
const hasMeaningfulConfig = configData &&
|
|
134
|
-
Object.keys(configData).some(key => !Object.prototype.hasOwnProperty.call(defaults, key) && key !==
|
|
135
|
-
configData.configs &&
|
|
138
|
+
Object.keys(configData).some((key) => !Object.prototype.hasOwnProperty.call(defaults, key) && key !== "_") &&
|
|
139
|
+
configData.configs &&
|
|
140
|
+
configData.configs.length > 0;
|
|
136
141
|
if (!hasMeaningfulConfig) {
|
|
137
142
|
// If rc didn't find it, try our manual search as a fallback
|
|
138
143
|
const foundPath = (0, exports.findConfigPath)();
|
|
@@ -150,7 +155,7 @@ const loadConfig = (appName = "apso", defaults = {}) => {
|
|
|
150
155
|
rootFolder: rootFolder,
|
|
151
156
|
entities: configData.entities || [],
|
|
152
157
|
apiType: configData.apiType || ApiType.Rest,
|
|
153
|
-
relationships: configData.relationships || []
|
|
158
|
+
relationships: configData.relationships || [],
|
|
154
159
|
};
|
|
155
160
|
if (version === 1) {
|
|
156
161
|
// Pass the correctly structured object
|
|
@@ -167,7 +172,7 @@ const loadConfig = (appName = "apso", defaults = {}) => {
|
|
|
167
172
|
if (!parsedData.entities || !Array.isArray(parsedData.entities)) {
|
|
168
173
|
throw new Error("Invalid config: 'entities' array is missing or invalid after parsing.");
|
|
169
174
|
}
|
|
170
|
-
if (!rootFolder || typeof rootFolder !==
|
|
175
|
+
if (!rootFolder || typeof rootFolder !== "string") {
|
|
171
176
|
throw new Error("Invalid config: 'rootFolder' string is missing or invalid.");
|
|
172
177
|
}
|
|
173
178
|
// Relationships are implicitly validated by parseRelationships/parseV1Relationships
|
|
@@ -177,7 +182,7 @@ const loadConfig = (appName = "apso", defaults = {}) => {
|
|
|
177
182
|
// or adjust UnifiedConfig. For now, returning empty array as placeholder.
|
|
178
183
|
// The relationshipMap is the primary output needed by downstream code.
|
|
179
184
|
relationships: [],
|
|
180
|
-
rootFolder: rootFolder
|
|
185
|
+
rootFolder: rootFolder,
|
|
181
186
|
};
|
|
182
187
|
};
|
|
183
188
|
exports.loadConfig = loadConfig;
|
package/dist/lib/controller.js
CHANGED
|
@@ -35,7 +35,9 @@ const createController = async (apiBaseDir, entity, relationshipMap) => {
|
|
|
35
35
|
const startNested = perf_hooks_1.performance.now();
|
|
36
36
|
const tmpDir = os_1.default.tmpdir();
|
|
37
37
|
const nestedJoinsFile = path.join(tmpDir, `apso-nested-joins-${entityName}-${Date.now()}.eta`);
|
|
38
|
-
const nestedJoinsStream = fs_1.default.createWriteStream(nestedJoinsFile, {
|
|
38
|
+
const nestedJoinsStream = fs_1.default.createWriteStream(nestedJoinsFile, {
|
|
39
|
+
encoding: "utf8",
|
|
40
|
+
});
|
|
39
41
|
let nestedCount = 0;
|
|
40
42
|
nestedCount = await (0, parse_1.streamNestedRelationships)(entityName, relationshipMap, nestedJoinsStream);
|
|
41
43
|
nestedJoinsStream.end();
|
package/dist/lib/dto.js
CHANGED
|
@@ -32,15 +32,15 @@ options) => {
|
|
|
32
32
|
// console.log('[DTO DEBUG] relationshipsTemplate:', relationshipsTemplate);
|
|
33
33
|
// }
|
|
34
34
|
// Add foreign key columns for ManyToOne relationships if not present
|
|
35
|
-
relationshipsTemplate.forEach(rel => {
|
|
35
|
+
relationshipsTemplate.forEach((rel) => {
|
|
36
36
|
var _a;
|
|
37
|
-
if (rel.type ===
|
|
38
|
-
const fkName = (0, casing_1.camelCase)(rel.referenceName || rel.name) +
|
|
37
|
+
if (rel.type === "ManyToOne") {
|
|
38
|
+
const fkName = (0, casing_1.camelCase)(rel.referenceName || rel.name) + "Id";
|
|
39
39
|
// Look up the referenced entity to determine the foreign key type
|
|
40
|
-
const referencedEntity = (_a = options === null || options === void 0 ? void 0 : options.allEntities) === null || _a === void 0 ? void 0 : _a.find(e => e.name === rel.name);
|
|
41
|
-
const referencedPrimaryKeyType = (referencedEntity === null || referencedEntity === void 0 ? void 0 : referencedEntity.primaryKeyType) ||
|
|
42
|
-
const fkDataType = referencedPrimaryKeyType ===
|
|
43
|
-
const fkType = referencedPrimaryKeyType ===
|
|
40
|
+
const referencedEntity = (_a = options === null || options === void 0 ? void 0 : options.allEntities) === null || _a === void 0 ? void 0 : _a.find((e) => e.name === rel.name);
|
|
41
|
+
const referencedPrimaryKeyType = (referencedEntity === null || referencedEntity === void 0 ? void 0 : referencedEntity.primaryKeyType) || "serial";
|
|
42
|
+
const fkDataType = referencedPrimaryKeyType === "uuid" ? "string" : "number";
|
|
43
|
+
const fkType = referencedPrimaryKeyType === "uuid" ? "text" : "integer";
|
|
44
44
|
// if (process.env.DEBUG) {
|
|
45
45
|
// console.log('[DTO DEBUG]', {
|
|
46
46
|
// relType: rel.type,
|
|
@@ -52,7 +52,7 @@ options) => {
|
|
|
52
52
|
// columns: columns.map(c => c.name)
|
|
53
53
|
// });
|
|
54
54
|
// }
|
|
55
|
-
if (!columns.some(col => col.name === fkName)) {
|
|
55
|
+
if (!columns.some((col) => col.name === fkName)) {
|
|
56
56
|
columns.push({
|
|
57
57
|
name: fkName,
|
|
58
58
|
dataType: fkDataType,
|
|
@@ -65,7 +65,7 @@ options) => {
|
|
|
65
65
|
const addDefaultPKProperty = primaryKeyColumns.length === 0;
|
|
66
66
|
const createdAt = entity.created_at;
|
|
67
67
|
const updatedAt = entity.updated_at;
|
|
68
|
-
const primaryKeyType = entity.primaryKeyType ||
|
|
68
|
+
const primaryKeyType = entity.primaryKeyType || "serial"; // Default to 'serial' if not specified
|
|
69
69
|
// Filter columns: remove id, createdAt, updatedAt
|
|
70
70
|
const dtoColumns = columns.filter((col) => !["id", "createdAt", "updatedAt"].includes(col.name));
|
|
71
71
|
// Class names
|
package/dist/lib/entity.js
CHANGED
|
@@ -21,15 +21,14 @@ const Eta = tslib_1.__importStar(require("eta"));
|
|
|
21
21
|
* @returns {Promise<void>} A promise that resolves when the entity file is created.
|
|
22
22
|
*/
|
|
23
23
|
const createEntity = async (options) => {
|
|
24
|
-
const { apiBaseDir, entity, relationships: relationshipsNew, apiType, allEntities } = options;
|
|
24
|
+
const { apiBaseDir, entity, relationships: relationshipsNew, apiType, allEntities, } = options;
|
|
25
25
|
const { name, fields = [], indexes, uniques } = entity;
|
|
26
26
|
const File = path.join(apiBaseDir, `${name}.entity.ts`);
|
|
27
27
|
const columns = (0, field_1.getFieldForTemplate)(fields, name);
|
|
28
|
-
const createPrimaryKey = columns.filter((column) => column.primary === true)
|
|
29
|
-
.length === 0;
|
|
28
|
+
const createPrimaryKey = columns.filter((column) => column.primary === true).length === 0;
|
|
30
29
|
const createdAt = entity.created_at;
|
|
31
30
|
const updatedAt = entity.updated_at;
|
|
32
|
-
const primaryKeyType = entity.primaryKeyType ||
|
|
31
|
+
const primaryKeyType = entity.primaryKeyType || "serial"; // Default to 'serial' if not specified
|
|
33
32
|
const relationships = (0, relationships_1.getRelationshipForTemplate)(name, relationshipsNew, allEntities);
|
|
34
33
|
const entitiesToImport = (0, relationships_1.getRelationshipsForImport)(name, relationships);
|
|
35
34
|
const data = {
|
|
@@ -48,7 +47,9 @@ const createEntity = async (options) => {
|
|
|
48
47
|
importEnums: (0, field_1.typeExistsInEntity)(entity, "enum") !== -1,
|
|
49
48
|
apiType,
|
|
50
49
|
};
|
|
51
|
-
const templatePath = apiType === "graphql"
|
|
50
|
+
const templatePath = apiType === "graphql"
|
|
51
|
+
? "./graphql/gql-entity-graphql"
|
|
52
|
+
: "./entities/entity";
|
|
52
53
|
const content = await Eta.renderFileAsync(templatePath, (0, file_system_1.withGeneratedMeta)(data));
|
|
53
54
|
await (0, file_system_1.createFile)(File, content);
|
|
54
55
|
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Entity, AuthConfig } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Represents a scope field configuration for template rendering
|
|
4
|
+
*/
|
|
5
|
+
interface ScopeFieldConfig {
|
|
6
|
+
field: string;
|
|
7
|
+
contextKey: string;
|
|
8
|
+
direct: boolean;
|
|
9
|
+
path?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Represents a scoped entity configuration for template rendering
|
|
13
|
+
*/
|
|
14
|
+
interface ScopedEntityConfig {
|
|
15
|
+
name: string;
|
|
16
|
+
routeName: string;
|
|
17
|
+
repoName: string;
|
|
18
|
+
scopes: ScopeFieldConfig[];
|
|
19
|
+
injectOnCreate: boolean;
|
|
20
|
+
enforceOn: string[];
|
|
21
|
+
bypassRoles: string[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Extracts scoped entity configurations from all entities.
|
|
25
|
+
*
|
|
26
|
+
* @param entities Array of all entities from the parsed .apsorc.
|
|
27
|
+
* @returns Array of scoped entity configurations for entities that have scopeBy defined.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getScopedEntities(entities: Entity[]): ScopedEntityConfig[];
|
|
30
|
+
/**
|
|
31
|
+
* Checks if any entities have scopeBy configured.
|
|
32
|
+
*
|
|
33
|
+
* @param entities Array of all entities from the parsed .apsorc.
|
|
34
|
+
* @returns True if at least one entity has scopeBy defined.
|
|
35
|
+
*/
|
|
36
|
+
export declare function hasScopedEntities(entities: Entity[]): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Generates the guards module directory and files.
|
|
39
|
+
* Creates auth.guard.ts, scope.guard.ts, guards.module.ts, and index.ts in the guards directory.
|
|
40
|
+
*
|
|
41
|
+
* @param rootPath The root path of the generated source (e.g., 'src').
|
|
42
|
+
* @param entities All entities from the parsed .apsorc.
|
|
43
|
+
* @param auth Optional auth configuration from .apsorc.
|
|
44
|
+
* @returns {Promise<void>} A promise that resolves when the guard files are created.
|
|
45
|
+
*/
|
|
46
|
+
export declare const createGuards: (rootPath: string, entities: Entity[], auth?: AuthConfig) => Promise<void>;
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createGuards = exports.hasScopedEntities = exports.getScopedEntities = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const Eta = tslib_1.__importStar(require("eta"));
|
|
6
|
+
const path = tslib_1.__importStar(require("path"));
|
|
7
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
8
|
+
const file_system_1 = require("./utils/file-system");
|
|
9
|
+
const types_1 = require("./types");
|
|
10
|
+
const pluralize_1 = tslib_1.__importDefault(require("pluralize"));
|
|
11
|
+
/**
|
|
12
|
+
* Default scope options when not specified
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_SCOPE_OPTIONS = {
|
|
15
|
+
injectOnCreate: true,
|
|
16
|
+
enforceOn: ["find", "get", "create", "update", "delete"],
|
|
17
|
+
bypassRoles: [],
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Parses scopeBy configuration and returns structured scope fields.
|
|
21
|
+
*
|
|
22
|
+
* @param scopeBy The scope configuration - either a single field/path string or an array of them.
|
|
23
|
+
* @returns An array of scope field configurations.
|
|
24
|
+
*/
|
|
25
|
+
function parseScopeBy(scopeBy) {
|
|
26
|
+
const scopes = Array.isArray(scopeBy) ? scopeBy : [scopeBy];
|
|
27
|
+
return scopes.map((scope) => {
|
|
28
|
+
const isDirect = !scope.includes(".");
|
|
29
|
+
const field = isDirect ? scope : scope.split(".")[0];
|
|
30
|
+
// For direct fields, contextKey is the same as field
|
|
31
|
+
// For nested paths like 'task.workspaceId', we extract 'workspaceId' as contextKey
|
|
32
|
+
const contextKey = isDirect ? scope : scope.split(".").pop();
|
|
33
|
+
return {
|
|
34
|
+
field,
|
|
35
|
+
contextKey,
|
|
36
|
+
direct: isDirect,
|
|
37
|
+
path: isDirect ? undefined : scope,
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Converts entity name to lowercase pluralized route name.
|
|
43
|
+
* e.g., "Project" -> "projects", "DiscoverySession" -> "discoverysessions"
|
|
44
|
+
*
|
|
45
|
+
* @param entityName The entity name in PascalCase.
|
|
46
|
+
* @returns The lowercase pluralized route name.
|
|
47
|
+
*/
|
|
48
|
+
function toRouteName(entityName) {
|
|
49
|
+
return (0, pluralize_1.default)(entityName).toLowerCase();
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Converts entity name to repository variable name.
|
|
53
|
+
* e.g., "Project" -> "projectRepository", "DiscoverySession" -> "discoverySessionRepository"
|
|
54
|
+
*
|
|
55
|
+
* @param entityName The entity name in PascalCase.
|
|
56
|
+
* @returns The camelCase repository variable name.
|
|
57
|
+
*/
|
|
58
|
+
function toRepoName(entityName) {
|
|
59
|
+
const camelCase = entityName.charAt(0).toLowerCase() + entityName.slice(1);
|
|
60
|
+
return `${camelCase}Repository`;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Extracts scoped entity configurations from all entities.
|
|
64
|
+
*
|
|
65
|
+
* @param entities Array of all entities from the parsed .apsorc.
|
|
66
|
+
* @returns Array of scoped entity configurations for entities that have scopeBy defined.
|
|
67
|
+
*/
|
|
68
|
+
function getScopedEntities(entities) {
|
|
69
|
+
return entities
|
|
70
|
+
.filter((entity) => entity.scopeBy)
|
|
71
|
+
.map((entity) => {
|
|
72
|
+
const options = { ...DEFAULT_SCOPE_OPTIONS, ...entity.scopeOptions };
|
|
73
|
+
return {
|
|
74
|
+
name: entity.name,
|
|
75
|
+
routeName: toRouteName(entity.name),
|
|
76
|
+
repoName: toRepoName(entity.name),
|
|
77
|
+
scopes: parseScopeBy(entity.scopeBy),
|
|
78
|
+
injectOnCreate: options.injectOnCreate,
|
|
79
|
+
enforceOn: options.enforceOn,
|
|
80
|
+
bypassRoles: options.bypassRoles,
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
exports.getScopedEntities = getScopedEntities;
|
|
85
|
+
/**
|
|
86
|
+
* Checks if any entities have scopeBy configured.
|
|
87
|
+
*
|
|
88
|
+
* @param entities Array of all entities from the parsed .apsorc.
|
|
89
|
+
* @returns True if at least one entity has scopeBy defined.
|
|
90
|
+
*/
|
|
91
|
+
function hasScopedEntities(entities) {
|
|
92
|
+
return entities.some((entity) => entity.scopeBy);
|
|
93
|
+
}
|
|
94
|
+
exports.hasScopedEntities = hasScopedEntities;
|
|
95
|
+
/**
|
|
96
|
+
* Normalizes auth config by applying defaults for DB session providers
|
|
97
|
+
* @param {AuthConfig | undefined} auth - The auth configuration to normalize
|
|
98
|
+
* @returns {DbSessionAuthConfig | undefined} The normalized auth config or undefined
|
|
99
|
+
*/
|
|
100
|
+
function normalizeAuthConfig(auth) {
|
|
101
|
+
if (!auth)
|
|
102
|
+
return undefined;
|
|
103
|
+
if ((0, types_1.isDbSessionAuth)(auth)) {
|
|
104
|
+
return {
|
|
105
|
+
...types_1.DB_SESSION_AUTH_DEFAULTS,
|
|
106
|
+
...auth,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// For now, only DB session providers are supported
|
|
110
|
+
// JWT providers will be added later
|
|
111
|
+
console.log(`[apso] Auth provider '${auth.provider}' is not yet supported, skipping auth guard generation`);
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Generates the guards module directory and files.
|
|
116
|
+
* Creates auth.guard.ts, scope.guard.ts, guards.module.ts, and index.ts in the guards directory.
|
|
117
|
+
*
|
|
118
|
+
* @param rootPath The root path of the generated source (e.g., 'src').
|
|
119
|
+
* @param entities All entities from the parsed .apsorc.
|
|
120
|
+
* @param auth Optional auth configuration from .apsorc.
|
|
121
|
+
* @returns {Promise<void>} A promise that resolves when the guard files are created.
|
|
122
|
+
*/
|
|
123
|
+
const createGuards = async (rootPath, entities, auth) => {
|
|
124
|
+
const hasScopes = hasScopedEntities(entities);
|
|
125
|
+
const normalizedAuth = normalizeAuthConfig(auth);
|
|
126
|
+
// Only generate guards if there are scoped entities OR auth config
|
|
127
|
+
if (!hasScopes && !normalizedAuth) {
|
|
128
|
+
console.log("[apso] No scopeBy or auth configurations found, skipping guards generation");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const guardsDir = path.join(rootPath, "guards");
|
|
132
|
+
// Ensure guards directory exists
|
|
133
|
+
if (!fs.existsSync(guardsDir)) {
|
|
134
|
+
fs.mkdirSync(guardsDir, { recursive: true });
|
|
135
|
+
}
|
|
136
|
+
const scopedEntities = getScopedEntities(entities);
|
|
137
|
+
// Common template data
|
|
138
|
+
const templateData = {
|
|
139
|
+
scopedEntities,
|
|
140
|
+
authConfig: normalizedAuth,
|
|
141
|
+
generatedAt: new Date().toISOString(),
|
|
142
|
+
generatedBy: "Apso CLI",
|
|
143
|
+
};
|
|
144
|
+
// Generate auth.guard.ts if auth is configured
|
|
145
|
+
if (normalizedAuth) {
|
|
146
|
+
const authGuardPath = path.join(guardsDir, "auth.guard.ts");
|
|
147
|
+
const authGuardContent = await Eta.renderFileAsync("./guards/auth.guard.eta", templateData);
|
|
148
|
+
await (0, file_system_1.createFile)(authGuardPath, authGuardContent);
|
|
149
|
+
console.log(`[apso] Generated ${authGuardPath}`);
|
|
150
|
+
}
|
|
151
|
+
// Generate scope.guard.ts if there are scoped entities
|
|
152
|
+
if (hasScopes) {
|
|
153
|
+
const scopeGuardPath = path.join(guardsDir, "scope.guard.ts");
|
|
154
|
+
const scopeGuardContent = await Eta.renderFileAsync("./guards/scope.guard.eta", templateData);
|
|
155
|
+
await (0, file_system_1.createFile)(scopeGuardPath, scopeGuardContent);
|
|
156
|
+
console.log(`[apso] Generated ${scopeGuardPath}`);
|
|
157
|
+
}
|
|
158
|
+
// Generate guards.module.ts
|
|
159
|
+
const guardsModulePath = path.join(guardsDir, "guards.module.ts");
|
|
160
|
+
const guardsModuleContent = await Eta.renderFileAsync("./guards/guards.module.eta", templateData);
|
|
161
|
+
await (0, file_system_1.createFile)(guardsModulePath, guardsModuleContent);
|
|
162
|
+
console.log(`[apso] Generated ${guardsModulePath}`);
|
|
163
|
+
// Generate index.ts
|
|
164
|
+
const indexPath = path.join(guardsDir, "index.ts");
|
|
165
|
+
const indexContent = await Eta.renderFileAsync("./guards/index.eta", templateData);
|
|
166
|
+
await (0, file_system_1.createFile)(indexPath, indexContent);
|
|
167
|
+
console.log(`[apso] Generated ${indexPath}`);
|
|
168
|
+
// Summary
|
|
169
|
+
const parts = [];
|
|
170
|
+
if (normalizedAuth)
|
|
171
|
+
parts.push(`auth (${normalizedAuth.provider})`);
|
|
172
|
+
if (scopedEntities.length > 0)
|
|
173
|
+
parts.push(`${scopedEntities.length} scoped entities`);
|
|
174
|
+
console.log(`[apso] Generated guards for ${parts.join(" + ")}`);
|
|
175
|
+
};
|
|
176
|
+
exports.createGuards = createGuards;
|
package/dist/lib/index.d.ts
CHANGED
package/dist/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createGqlDTO = exports.createEnums = exports.createDto = exports.createIndexAppModule = exports.createModule = exports.createService = exports.createController = exports.createEntity = exports.parseApsorc = void 0;
|
|
3
|
+
exports.getScopedEntities = exports.hasScopedEntities = exports.createGuards = exports.createGqlDTO = exports.createEnums = exports.createDto = exports.createIndexAppModule = exports.createModule = exports.createService = exports.createController = exports.createEntity = exports.parseApsorc = void 0;
|
|
4
4
|
var apsorc_parser_1 = require("./apsorc-parser");
|
|
5
5
|
Object.defineProperty(exports, "parseApsorc", { enumerable: true, get: function () { return apsorc_parser_1.parseApsorc; } });
|
|
6
6
|
var entity_1 = require("./entity");
|
|
@@ -19,3 +19,7 @@ var enums_1 = require("./enums");
|
|
|
19
19
|
Object.defineProperty(exports, "createEnums", { enumerable: true, get: function () { return enums_1.createEnums; } });
|
|
20
20
|
var gql_dto_1 = require("./gql-dto");
|
|
21
21
|
Object.defineProperty(exports, "createGqlDTO", { enumerable: true, get: function () { return gql_dto_1.createGqlDTO; } });
|
|
22
|
+
var guards_1 = require("./guards");
|
|
23
|
+
Object.defineProperty(exports, "createGuards", { enumerable: true, get: function () { return guards_1.createGuards; } });
|
|
24
|
+
Object.defineProperty(exports, "hasScopedEntities", { enumerable: true, get: function () { return guards_1.hasScopedEntities; } });
|
|
25
|
+
Object.defineProperty(exports, "getScopedEntities", { enumerable: true, get: function () { return guards_1.getScopedEntities; } });
|
package/dist/lib/module.js
CHANGED
|
@@ -31,7 +31,9 @@ const createModule = async (apiBaseDir, entity, options) => {
|
|
|
31
31
|
entityName,
|
|
32
32
|
};
|
|
33
33
|
// Determine template based on API type
|
|
34
|
-
const templatePath = (options === null || options === void 0 ? void 0 : options.apiType) === "graphql"
|
|
34
|
+
const templatePath = (options === null || options === void 0 ? void 0 : options.apiType) === "graphql"
|
|
35
|
+
? "./graphql/gql-module-graphql"
|
|
36
|
+
: "./rest/module-rest";
|
|
35
37
|
// Render the template
|
|
36
38
|
const content = await Eta.renderFileAsync(templatePath, data);
|
|
37
39
|
// Create the module file
|