@pattern-stack/codegen 0.6.7 → 0.7.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/CHANGELOG.md +12 -0
- package/dist/src/cli/index.js +516 -73
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/index.d.ts +208 -1
- package/dist/src/index.js +147 -0
- package/dist/src/index.js.map +1 -1
- package/package.json +1 -1
- package/src/patterns/library/base-junction-fields.ts +32 -0
- package/src/patterns/library/index.ts +7 -0
- package/src/patterns/library/junction.pattern.ts +41 -0
- package/templates/entity/new/backend/application/queries/get-by-id.ejs.t +3 -3
- package/templates/entity/new/backend/application/queries/grouped-index.ejs.t +5 -5
- package/templates/entity/new/backend/application/queries/index.ejs.t +3 -0
- package/templates/entity/new/backend/application/queries/list.ejs.t +3 -3
- package/templates/entity/new/backend/application/queries/relationships.queries.ejs.t +147 -0
- package/templates/entity/new/backend/database/repository.ejs.t +36 -176
- package/templates/entity/new/backend/domain/entity.ejs.t +0 -44
- package/templates/entity/new/backend/domain/grouped-index.ejs.t +4 -60
- package/templates/entity/new/backend/domain/index.ejs.t +2 -2
- package/templates/entity/new/backend/domain/repository-interface.ejs.t +16 -17
- package/templates/entity/new/backend/modules/core/module.ejs.t +10 -0
- package/templates/entity/new/backend/presentation/controller.ejs.t +2 -34
- package/templates/entity/new/clean-lite-ps/entity.ejs.t +21 -2
- package/templates/entity/new/clean-lite-ps/module.ejs.t +27 -2
- package/templates/entity/new/clean-lite-ps/prompt-extension.js +108 -9
- package/templates/entity/new/clean-lite-ps/repository.ejs.t +33 -1
- package/templates/entity/new/clean-lite-ps/service.ejs.t +79 -0
- package/templates/entity/new/prompt.js +1 -0
- package/templates/junction/new/_inject-parent-module-clp-left.ejs.t +8 -0
- package/templates/junction/new/_inject-parent-module-clp-right.ejs.t +8 -0
- package/templates/junction/new/_inject-parent-module-import-clp-left.ejs.t +9 -0
- package/templates/junction/new/_inject-parent-module-import-clp-right.ejs.t +9 -0
- package/templates/junction/new/_inject-parent-service-clp-left.ejs.t +51 -0
- package/templates/junction/new/_inject-parent-service-clp-right.ejs.t +48 -0
- package/templates/junction/new/_inject-parent-service-import-clp-left.ejs.t +11 -0
- package/templates/junction/new/_inject-parent-service-import-clp-right.ejs.t +11 -0
- package/templates/junction/new/entity.ejs.t +111 -0
- package/templates/junction/new/index.ejs.t +15 -0
- package/templates/junction/new/module.ejs.t +37 -0
- package/templates/junction/new/prompt.js +492 -0
- package/templates/junction/new/repository.ejs.t +67 -0
- package/templates/junction/new/service.ejs.t +174 -0
|
@@ -16,23 +16,6 @@ force: true
|
|
|
16
16
|
<% if (hasEntityRefFields) { -%>
|
|
17
17
|
import type { EntityType } from '<%= locations.dbSchemaServer.import %>';
|
|
18
18
|
<% } -%>
|
|
19
|
-
<%
|
|
20
|
-
// Collect unique non-self-referential imports
|
|
21
|
-
const importedEntities = new Set();
|
|
22
|
-
[...belongsToRelations, ...hasManyRelations, ...hasOneRelations].forEach((rel) => {
|
|
23
|
-
if (rel.target !== name) {
|
|
24
|
-
importedEntities.add(rel.target);
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
-%>
|
|
28
|
-
<% if (importedEntities.size > 0) { -%>
|
|
29
|
-
|
|
30
|
-
<% importedEntities.forEach((target) => {
|
|
31
|
-
const targetClass = target.charAt(0).toUpperCase() + target.slice(1).replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
32
|
-
-%>
|
|
33
|
-
import type { <%= targetClass %> } from '../<%= target %>/<%= target %>.entity';
|
|
34
|
-
<% }) -%>
|
|
35
|
-
<% } -%>
|
|
36
19
|
|
|
37
20
|
export class <%= className %> {
|
|
38
21
|
constructor(
|
|
@@ -51,28 +34,12 @@ export class <%= className %> {
|
|
|
51
34
|
public readonly validFrom: Date | null,
|
|
52
35
|
public readonly validTo: Date | null,
|
|
53
36
|
public readonly isActive: boolean,
|
|
54
|
-
<% } -%>
|
|
55
|
-
<% if (hasRelationships) { -%>
|
|
56
|
-
// Loaded relations (optional, populated when eager-loaded)
|
|
57
|
-
<% belongsToRelations.forEach((rel) => { -%>
|
|
58
|
-
public readonly <%= rel.name %>?: <%= rel.targetClass %>,
|
|
59
|
-
<% }) -%>
|
|
60
|
-
<% hasManyRelations.forEach((rel) => { -%>
|
|
61
|
-
public readonly <%= rel.name %>?: <%= rel.targetClass %>[],
|
|
62
|
-
<% }) -%>
|
|
63
|
-
<% hasOneRelations.forEach((rel) => { -%>
|
|
64
|
-
public readonly <%= rel.name %>?: <%= rel.targetClass %>,
|
|
65
|
-
<% }) -%>
|
|
66
37
|
<% } -%>
|
|
67
38
|
) {}
|
|
68
39
|
|
|
69
40
|
static fromRecord(
|
|
70
41
|
// biome-ignore lint/suspicious/noExplicitAny: Drizzle records have dynamic shape
|
|
71
42
|
record: Record<string, any>,
|
|
72
|
-
<% if (hasRelationships) { -%>
|
|
73
|
-
// biome-ignore lint/suspicious/noExplicitAny: Returns different entity types
|
|
74
|
-
mapRelation?: (name: string, data: unknown) => any,
|
|
75
|
-
<% } -%>
|
|
76
43
|
): <%= className %> {
|
|
77
44
|
return new <%= className %>(
|
|
78
45
|
record.id,
|
|
@@ -90,17 +57,6 @@ export class <%= className %> {
|
|
|
90
57
|
record.validFrom,
|
|
91
58
|
record.validTo,
|
|
92
59
|
record.isActive,
|
|
93
|
-
<% } -%>
|
|
94
|
-
<% if (hasRelationships) { -%>
|
|
95
|
-
<% belongsToRelations.forEach((rel) => { -%>
|
|
96
|
-
record.<%= rel.name %> ? mapRelation?.('<%= rel.name %>', record.<%= rel.name %>) : undefined,
|
|
97
|
-
<% }) -%>
|
|
98
|
-
<% hasManyRelations.forEach((rel) => { -%>
|
|
99
|
-
record.<%= rel.name %> ? mapRelation?.('<%= rel.name %>', record.<%= rel.name %>) : undefined,
|
|
100
|
-
<% }) -%>
|
|
101
|
-
<% hasOneRelations.forEach((rel) => { -%>
|
|
102
|
-
record.<%= rel.name %> ? mapRelation?.('<%= rel.name %>', record.<%= rel.name %>) : undefined,
|
|
103
|
-
<% }) -%>
|
|
104
60
|
<% } -%>
|
|
105
61
|
);
|
|
106
62
|
}
|
|
@@ -14,23 +14,6 @@ force: true
|
|
|
14
14
|
<% if (hasEntityRefFields) { -%>
|
|
15
15
|
import type { EntityType } from '<%= locations.dbSchemaServer.import %>';
|
|
16
16
|
<% } -%>
|
|
17
|
-
<%
|
|
18
|
-
// Collect unique non-self-referential imports
|
|
19
|
-
const importedEntities = new Set();
|
|
20
|
-
[...belongsToRelations, ...hasManyRelations, ...hasOneRelations].forEach((rel) => {
|
|
21
|
-
if (rel.target !== name) {
|
|
22
|
-
importedEntities.add(rel.target);
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
-%>
|
|
26
|
-
<% if (importedEntities.size > 0) { -%>
|
|
27
|
-
|
|
28
|
-
<% importedEntities.forEach((target) => {
|
|
29
|
-
const targetClass = target.charAt(0).toUpperCase() + target.slice(1).replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
30
|
-
-%>
|
|
31
|
-
import type { <%= targetClass %> } from '../<%= target %>';
|
|
32
|
-
<% }) -%>
|
|
33
|
-
<% } -%>
|
|
34
17
|
|
|
35
18
|
// ============================================================================
|
|
36
19
|
// Entity
|
|
@@ -53,28 +36,12 @@ export class <%= className %> {
|
|
|
53
36
|
public readonly validFrom: Date | null,
|
|
54
37
|
public readonly validTo: Date | null,
|
|
55
38
|
public readonly isActive: boolean,
|
|
56
|
-
<% } -%>
|
|
57
|
-
<% if (hasRelationships) { -%>
|
|
58
|
-
// Loaded relations (optional, populated when eager-loaded)
|
|
59
|
-
<% belongsToRelations.forEach((rel) => { -%>
|
|
60
|
-
public readonly <%= rel.name %>?: <%= rel.targetClass %>,
|
|
61
|
-
<% }) -%>
|
|
62
|
-
<% hasManyRelations.forEach((rel) => { -%>
|
|
63
|
-
public readonly <%= rel.name %>?: <%= rel.targetClass %>[],
|
|
64
|
-
<% }) -%>
|
|
65
|
-
<% hasOneRelations.forEach((rel) => { -%>
|
|
66
|
-
public readonly <%= rel.name %>?: <%= rel.targetClass %>,
|
|
67
|
-
<% }) -%>
|
|
68
39
|
<% } -%>
|
|
69
40
|
) {}
|
|
70
41
|
|
|
71
42
|
static fromRecord(
|
|
72
43
|
// biome-ignore lint/suspicious/noExplicitAny: Drizzle records have dynamic shape
|
|
73
44
|
record: Record<string, any>,
|
|
74
|
-
<% if (hasRelationships) { -%>
|
|
75
|
-
// biome-ignore lint/suspicious/noExplicitAny: Returns different entity types
|
|
76
|
-
mapRelation?: (name: string, data: unknown) => any,
|
|
77
|
-
<% } -%>
|
|
78
45
|
): <%= className %> {
|
|
79
46
|
return new <%= className %>(
|
|
80
47
|
record.id,
|
|
@@ -92,17 +59,6 @@ export class <%= className %> {
|
|
|
92
59
|
record.validFrom,
|
|
93
60
|
record.validTo,
|
|
94
61
|
record.isActive,
|
|
95
|
-
<% } -%>
|
|
96
|
-
<% if (hasRelationships) { -%>
|
|
97
|
-
<% belongsToRelations.forEach((rel) => { -%>
|
|
98
|
-
record.<%= rel.name %> ? mapRelation?.('<%= rel.name %>', record.<%= rel.name %>) : undefined,
|
|
99
|
-
<% }) -%>
|
|
100
|
-
<% hasManyRelations.forEach((rel) => { -%>
|
|
101
|
-
record.<%= rel.name %> ? mapRelation?.('<%= rel.name %>', record.<%= rel.name %>) : undefined,
|
|
102
|
-
<% }) -%>
|
|
103
|
-
<% hasOneRelations.forEach((rel) => { -%>
|
|
104
|
-
record.<%= rel.name %> ? mapRelation?.('<%= rel.name %>', record.<%= rel.name %>) : undefined,
|
|
105
|
-
<% }) -%>
|
|
106
62
|
<% } -%>
|
|
107
63
|
);
|
|
108
64
|
}
|
|
@@ -111,18 +67,6 @@ export class <%= className %> {
|
|
|
111
67
|
// ============================================================================
|
|
112
68
|
// Repository Interface
|
|
113
69
|
// ============================================================================
|
|
114
|
-
<% if (hasRelationships) { -%>
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Type-safe eager loading options.
|
|
118
|
-
* Pass to repository methods to include related entities.
|
|
119
|
-
*/
|
|
120
|
-
export type <%= className %>With = {
|
|
121
|
-
<% relationships.forEach((rel) => { -%>
|
|
122
|
-
<%= rel.name %>?: boolean;
|
|
123
|
-
<% }) -%>
|
|
124
|
-
};
|
|
125
|
-
<% } -%>
|
|
126
70
|
|
|
127
71
|
/**
|
|
128
72
|
* Domain-level input types for repository operations.
|
|
@@ -143,8 +87,8 @@ export type Update<%= className %>Input = Partial<Create<%= className %>Input>;
|
|
|
143
87
|
|
|
144
88
|
export interface I<%= className %>Repository {
|
|
145
89
|
create(input: Create<%= className %>Input): Promise<<%= className %>>;
|
|
146
|
-
findById(id: string
|
|
147
|
-
findAll(
|
|
90
|
+
findById(id: string): Promise<<%= className %> | null>;
|
|
91
|
+
findAll(): Promise<<%= className %>[]>;
|
|
148
92
|
update(id: string, input: Update<%= className %>Input): Promise<<%= className %> | null>;
|
|
149
93
|
delete(id: string): Promise<<%= className %> | null>;
|
|
150
94
|
<% if (hasSoftDelete) { -%>
|
|
@@ -154,10 +98,10 @@ export interface I<%= className %>Repository {
|
|
|
154
98
|
findOnlyDeleted(): Promise<<%= className %>[]>;
|
|
155
99
|
<% } -%>
|
|
156
100
|
<% belongsToRelations.forEach((rel) => { -%>
|
|
157
|
-
findBy<%= rel.foreignKeyPascal %>(id: string
|
|
101
|
+
findBy<%= rel.foreignKeyPascal %>(id: string, opts?: { cursor?: string; limit?: number }): Promise<<%= className %>[]>;
|
|
158
102
|
<% }) -%>
|
|
159
103
|
<% entityRefFields.forEach((ref) => { -%>
|
|
160
|
-
findBy<%= ref.pascalName %>(entityType: EntityType, entityId: string
|
|
104
|
+
findBy<%= ref.pascalName %>(entityType: EntityType, entityId: string): Promise<<%= className %>[]>;
|
|
161
105
|
<% }) -%>
|
|
162
106
|
}
|
|
163
107
|
<% } -%>
|
|
@@ -11,5 +11,5 @@ force: true
|
|
|
11
11
|
* when using separate file layout (file_grouping: "separate")
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
export * from './<%=
|
|
15
|
-
export * from './<%=
|
|
14
|
+
export * from './<%= fileNames.entity.replace('.ts', '') %>';
|
|
15
|
+
export * from './<%= fileNames.repositoryInterface.replace('.ts', '') %>';
|
|
@@ -9,25 +9,13 @@ force: true
|
|
|
9
9
|
* Generated by entity codegen - do not edit directly
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import type { <%= className %> } from './<%= name
|
|
12
|
+
import type { <%= className %> } from './<%= (typeof fileNames !== 'undefined' ? fileNames.entity : name + '.entity.ts').replace('.ts', '') %>';
|
|
13
13
|
<% if (hasEntityRefFields) { -%>
|
|
14
14
|
import type { EntityType } from '<%= locations.dbSchemaServer.import %>';
|
|
15
15
|
<% } -%>
|
|
16
16
|
<% if (hasEmits && (createEventType || updateEventType || deleteEventType)) { -%>
|
|
17
17
|
import type { DrizzleTransaction } from '<%= eventsTokenImport %>';
|
|
18
18
|
<% } -%>
|
|
19
|
-
<% if (hasRelationships) { -%>
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Type-safe eager loading options.
|
|
23
|
-
* Pass to repository methods to include related entities.
|
|
24
|
-
*/
|
|
25
|
-
export type <%= className %>With = {
|
|
26
|
-
<% relationships.forEach((rel) => { -%>
|
|
27
|
-
<%= rel.name %>?: boolean;
|
|
28
|
-
<% }) -%>
|
|
29
|
-
};
|
|
30
|
-
<% } -%>
|
|
31
19
|
|
|
32
20
|
/**
|
|
33
21
|
* Domain-level input types for repository operations.
|
|
@@ -48,8 +36,8 @@ export type Update<%= className %>Input = Partial<Create<%= className %>Input>;
|
|
|
48
36
|
|
|
49
37
|
export interface I<%= className %>Repository {
|
|
50
38
|
create(input: Create<%= className %>Input<%= (hasEmits && createEventType) ? ', tx?: DrizzleTransaction' : '' %>): Promise<<%= className %>>;
|
|
51
|
-
findById(id: string
|
|
52
|
-
findAll(
|
|
39
|
+
findById(id: string): Promise<<%= className %> | null>;
|
|
40
|
+
findAll(): Promise<<%= className %>[]>;
|
|
53
41
|
update(id: string, input: Update<%= className %>Input<%= (hasEmits && updateEventType) ? ', tx?: DrizzleTransaction' : '' %>): Promise<<%= className %> | null>;
|
|
54
42
|
delete(id: string<%= (hasEmits && deleteEventType) ? ', tx?: DrizzleTransaction' : '' %>): Promise<<%= className %> | null>;
|
|
55
43
|
<% if (hasSoftDelete) { -%>
|
|
@@ -58,16 +46,27 @@ export interface I<%= className %>Repository {
|
|
|
58
46
|
findWithDeleted(): Promise<<%= className %>[]>;
|
|
59
47
|
findOnlyDeleted(): Promise<<%= className %>[]>;
|
|
60
48
|
<% } -%>
|
|
49
|
+
<%_
|
|
50
|
+
// CGP-358: FK methods with opts take priority. Always emit FK signatures with opts.
|
|
51
|
+
// Skip declarative query signature when FK covers the same method name.
|
|
52
|
+
const _riFkMethodNames = new Set(belongsToRelations.map(rel => `findBy${rel.foreignKeyPascal}`));
|
|
53
|
+
_%>
|
|
61
54
|
<% belongsToRelations.forEach((rel) => { -%>
|
|
62
|
-
findBy<%= rel.foreignKeyPascal %>(id: string
|
|
55
|
+
findBy<%= rel.foreignKeyPascal %>(id: string, opts?: { cursor?: string; limit?: number }): Promise<<%= className %>[]>;
|
|
63
56
|
<% }) -%>
|
|
64
57
|
<% entityRefFields.forEach((ref) => { -%>
|
|
65
|
-
findBy<%= ref.pascalName %>(entityType: EntityType, entityId: string
|
|
58
|
+
findBy<%= ref.pascalName %>(entityType: EntityType, entityId: string): Promise<<%= className %>[]>;
|
|
66
59
|
<% }) -%>
|
|
67
60
|
<% if (hasDeclarativeQueries) { -%>
|
|
68
61
|
// Declarative queries (from queries: block in entity YAML)
|
|
69
62
|
<% processedQueries.forEach((q) => { -%>
|
|
63
|
+
<%_
|
|
64
|
+
// Skip declarative signature when FK method covers it (opts is a superset of plain single-param).
|
|
65
|
+
const _skipRiDq = _riFkMethodNames.has(q.methodName) && !q.isUnique && !q.hasVia && !q.hasSelect;
|
|
66
|
+
_%>
|
|
67
|
+
<% if (!_skipRiDq) { -%>
|
|
70
68
|
<%= q.methodName %>(<%- q.params.map(p => `${p.camelName}: ${p.tsType}`).join(', ') %>): Promise<<%- q.returnType %>>;
|
|
69
|
+
<% } -%>
|
|
71
70
|
<% }) -%>
|
|
72
71
|
<% } -%>
|
|
73
72
|
}
|
|
@@ -32,6 +32,9 @@ import { <%= className %>Repository } from '<%= imports.moduleToRepository %>';
|
|
|
32
32
|
<% if (hasDeclarativeQueries) { -%>
|
|
33
33
|
import { declarativeQueryClasses } from '<%= imports.moduleToDeclarativeQueries %>';
|
|
34
34
|
<% } -%>
|
|
35
|
+
<% if (hasRelationships && !isGrouped) { -%>
|
|
36
|
+
import { relationshipQueryClasses } from '<%= imports.moduleToRelationshipQueries %>';
|
|
37
|
+
<% } -%>
|
|
35
38
|
<% if (exposeRest || exposeElectric) { -%>
|
|
36
39
|
import { <%= classNamePlural %>Controller } from '<%= imports.moduleToController %>';
|
|
37
40
|
<% } -%>
|
|
@@ -58,6 +61,10 @@ import { create<%= className %>Schema, update<%= className %>Schema, <%= camelNa
|
|
|
58
61
|
<% if (hasDeclarativeQueries) { -%>
|
|
59
62
|
// Declarative queries
|
|
60
63
|
...declarativeQueryClasses,
|
|
64
|
+
<% } -%>
|
|
65
|
+
<% if (hasRelationships && !isGrouped) { -%>
|
|
66
|
+
// Relationship composition queries
|
|
67
|
+
...relationshipQueryClasses,
|
|
61
68
|
<% } -%>
|
|
62
69
|
],
|
|
63
70
|
exports: [
|
|
@@ -71,6 +78,9 @@ import { create<%= className %>Schema, update<%= className %>Schema, <%= camelNa
|
|
|
71
78
|
<%= deleteCommandClass %>,
|
|
72
79
|
<% if (hasDeclarativeQueries) { -%>
|
|
73
80
|
...declarativeQueryClasses,
|
|
81
|
+
<% } -%>
|
|
82
|
+
<% if (hasRelationships && !isGrouped) { -%>
|
|
83
|
+
...relationshipQueryClasses,
|
|
74
84
|
<% } -%>
|
|
75
85
|
],
|
|
76
86
|
})
|
|
@@ -19,9 +19,6 @@ import {
|
|
|
19
19
|
ParseUUIDPipe,
|
|
20
20
|
Post,
|
|
21
21
|
Put,
|
|
22
|
-
<% if (hasRelationships) { -%>
|
|
23
|
-
Query,
|
|
24
|
-
<% } -%>
|
|
25
22
|
UsePipes,
|
|
26
23
|
} from '@nestjs/common';
|
|
27
24
|
import {
|
|
@@ -44,9 +41,6 @@ import { <%= deleteCommandClass %> } from '<%= imports.controllerToDeleteCommand
|
|
|
44
41
|
import { <%= updateCommandClass %> } from '<%= imports.controllerToUpdateCommand %>';
|
|
45
42
|
import { ZodValidationPipe } from '../../core/pipes/zod-validation.pipe';
|
|
46
43
|
import { <%= className %> } from '<%= imports.controllerToDomain %>';
|
|
47
|
-
<% if (hasRelationships) { -%>
|
|
48
|
-
import type { <%= className %>With } from '<%= imports.controllerToDomain %>';
|
|
49
|
-
<% } -%>
|
|
50
44
|
|
|
51
45
|
// OPENAPI-3: controller decorators reference schemas by `$ref` rather
|
|
52
46
|
// than `type:` class references because generated DTOs are Zod-derived
|
|
@@ -72,12 +66,8 @@ export class <%= classNamePlural %>Controller {
|
|
|
72
66
|
})
|
|
73
67
|
@ApiResponse({ status: 401, schema: { $ref: '#/components/schemas/ErrorResponseDto' } })
|
|
74
68
|
@Get()
|
|
75
|
-
async findAll(
|
|
76
|
-
<% if (hasRelationships) { -%>
|
|
77
|
-
return this.list<%= classNamePlural %>Query.execute(this.parseInclude(include));
|
|
78
|
-
<% } else { -%>
|
|
69
|
+
async findAll(): Promise<<%= className %>[]> {
|
|
79
70
|
return this.list<%= classNamePlural %>Query.execute();
|
|
80
|
-
<% } -%>
|
|
81
71
|
}
|
|
82
72
|
|
|
83
73
|
@ApiOperation({ summary: 'Find <%= name %> by id', operationId: 'find<%= className %>ById' })
|
|
@@ -88,15 +78,8 @@ export class <%= classNamePlural %>Controller {
|
|
|
88
78
|
@Get(':id')
|
|
89
79
|
async findById(
|
|
90
80
|
@Param('id', ParseUUIDPipe) id: string,
|
|
91
|
-
<% if (hasRelationships) { -%>
|
|
92
|
-
@Query('include') include?: string,
|
|
93
|
-
<% } -%>
|
|
94
81
|
): Promise<<%= className %>> {
|
|
95
|
-
<% if (hasRelationships) { -%>
|
|
96
|
-
return this.get<%= className %>ByIdQuery.execute(id, this.parseInclude(include));
|
|
97
|
-
<% } else { -%>
|
|
98
82
|
return this.get<%= className %>ByIdQuery.execute(id);
|
|
99
|
-
<% } -%>
|
|
100
83
|
}
|
|
101
84
|
|
|
102
85
|
@ApiOperation({ summary: 'Create <%= name %>', operationId: 'create<%= className %>' })
|
|
@@ -144,23 +127,8 @@ export class <%= classNamePlural %>Controller {
|
|
|
144
127
|
): Promise<<%= className %>> {
|
|
145
128
|
return this.delete<%= className %>Command.execute(id, { actor: { tenantId, userId } });
|
|
146
129
|
}
|
|
147
|
-
<% if (hasRelationships) { -%>
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Parse comma-separated include query param into typed options.
|
|
151
|
-
* Example: ?include=account,owner → { account: true, owner: true }
|
|
152
|
-
*/
|
|
153
|
-
private parseInclude(include?: string): <%= className %>With | undefined {
|
|
154
|
-
if (!include) return undefined;
|
|
155
|
-
const parts = include.split(',').map((s) => s.trim());
|
|
156
|
-
return {
|
|
157
|
-
<% relationships.forEach((rel) => { -%>
|
|
158
|
-
<%= rel.name %>: parts.includes('<%= rel.name %>'),
|
|
159
|
-
<% }) -%>
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
<% } -%>
|
|
163
130
|
}
|
|
131
|
+
|
|
164
132
|
<% } -%>
|
|
165
133
|
<% if (exposeElectric) { -%>
|
|
166
134
|
/**
|
|
@@ -7,6 +7,9 @@ import {
|
|
|
7
7
|
<%_ clpDrizzleImports.filter(i => i !== 'relations').forEach(i => { _%>
|
|
8
8
|
<%= i %>,
|
|
9
9
|
<%_ }) _%>
|
|
10
|
+
<%_ if (typeof clpHasSelfFk !== 'undefined' && clpHasSelfFk) { _%>
|
|
11
|
+
type AnyPgColumn,
|
|
12
|
+
<%_ } _%>
|
|
10
13
|
} from 'drizzle-orm/pg-core';
|
|
11
14
|
<%_ if (clpHasRelationsBlock) { _%>
|
|
12
15
|
import { relations, type InferSelectModel } from 'drizzle-orm';
|
|
@@ -18,6 +21,18 @@ import { type InferSelectModel } from 'drizzle-orm';
|
|
|
18
21
|
import { <%= rel.relatedTable %> } from '<%= rel.importPath %>';
|
|
19
22
|
<%_ } _%>
|
|
20
23
|
<%_ }) _%>
|
|
24
|
+
<%_ /* CGP-358b: import has_many target tables for many() relation const */ _%>
|
|
25
|
+
<%_ if (typeof clpExistingHasMany !== 'undefined') { _%>
|
|
26
|
+
<%_ clpExistingHasMany.filter(rel => !rel.isSelfRef).forEach(rel => { _%>
|
|
27
|
+
import { <%= rel.targetPlural %> } from '../<%= rel.targetPlural %>/<%= rel.target %>.entity';
|
|
28
|
+
<%_ }) _%>
|
|
29
|
+
<%_ } _%>
|
|
30
|
+
<%_ if (typeof clpEnumFields !== 'undefined' && clpEnumFields.length > 0) { _%>
|
|
31
|
+
|
|
32
|
+
<%_ clpEnumFields.forEach(ef => { _%>
|
|
33
|
+
export const <%= ef.enumName %> = pgEnum('<%= ef.dbName %>', [<%- ef.choices.map(c => `'${c}'`).join(', ') %>]);
|
|
34
|
+
<%_ }) _%>
|
|
35
|
+
<%_ } _%>
|
|
21
36
|
|
|
22
37
|
export const <%= entityNamePlural %> = pgTable(
|
|
23
38
|
'<%= entityNamePlural %>',
|
|
@@ -30,7 +45,7 @@ export const <%= entityNamePlural %> = pgTable(
|
|
|
30
45
|
// cascade rules never fire for a soft-deleted parent. This FK constraint only applies on
|
|
31
46
|
// hard-delete (e.g. admin purge). See ADR-021: docs/adrs/ADR-021-on-delete-semantics.md
|
|
32
47
|
<%_ } _%>
|
|
33
|
-
<%= rel.camelField %>: uuid('<%= rel.field %>')<%= rel.nullable ? '' : '.notNull()' %>.references(()
|
|
48
|
+
<%= rel.camelField %>: uuid('<%= rel.field %>')<%= rel.nullable ? '' : '.notNull()' %>.references(<%= rel.isSelfFk ? '(): AnyPgColumn ' : '() ' %>=> <%= rel.relatedTable %>.id, { onDelete: '<%= rel.onDelete %>' }),
|
|
34
49
|
<%_ }) _%>
|
|
35
50
|
<%_ clpProcessedFields.forEach(field => { _%>
|
|
36
51
|
<%= field.camelName %>: <%- field.drizzleChain %>,
|
|
@@ -51,14 +66,18 @@ export const <%= entityNamePlural %> = pgTable(
|
|
|
51
66
|
},
|
|
52
67
|
);
|
|
53
68
|
<%_ if (clpHasRelationsBlock) { _%>
|
|
69
|
+
<%_ const needsMany = typeof clpExistingHasMany !== 'undefined' && clpExistingHasMany.length > 0; _%>
|
|
54
70
|
|
|
55
|
-
export const <%= entityNamePlural %>Relations = relations(<%= entityNamePlural %>, ({ one }) => ({
|
|
71
|
+
export const <%= entityNamePlural %>Relations = relations(<%= entityNamePlural %>, ({ one<%= needsMany ? ', many' : '' %> }) => ({
|
|
56
72
|
<%_ clpBelongsTo.forEach(rel => { _%>
|
|
57
73
|
<%= rel.relationKey %>: one(<%= rel.relatedTable %>, {
|
|
58
74
|
fields: [<%= entityNamePlural %>.<%= rel.camelField %>],
|
|
59
75
|
references: [<%= rel.relatedTable %>.id],
|
|
60
76
|
}),
|
|
61
77
|
<%_ }) _%>
|
|
78
|
+
<%_ if (typeof clpExistingHasMany !== 'undefined') { clpExistingHasMany.forEach(rel => { _%>
|
|
79
|
+
<%= rel.name %>: many(<%= rel.targetPlural %>),
|
|
80
|
+
<%_ }) } _%>
|
|
62
81
|
}));
|
|
63
82
|
<%_ } _%>
|
|
64
83
|
|
|
@@ -13,9 +13,21 @@ force: true
|
|
|
13
13
|
import { Inject, Module, type OnModuleInit } from '@nestjs/common';
|
|
14
14
|
import { OPENAPI_REGISTRY, type OpenApiRegistry } from '@shared/openapi';
|
|
15
15
|
import { DatabaseModule } from '@shared/database/database.module';
|
|
16
|
-
<%_
|
|
17
|
-
|
|
16
|
+
<%_ /* CGP-358b: Import cross-entity repos needed for has_many composition */ _%>
|
|
17
|
+
<%_ if (typeof clpExistingHasMany !== 'undefined') { _%>
|
|
18
|
+
<%_ const hasManyNeedingImport = clpExistingHasMany.filter(r => !r.isSelfRef); _%>
|
|
19
|
+
<%_ const uniqueHasManyForModule = [...new Map(hasManyNeedingImport.map(r => [r.target, r])).values()]; _%>
|
|
20
|
+
<%_ uniqueHasManyForModule.forEach(rel => { _%>
|
|
21
|
+
import { <%= rel.targetClass %>Repository } from '../<%= rel.targetPlural %>/<%= rel.target %>.repository';
|
|
22
|
+
<%_ }) _%>
|
|
23
|
+
<%_ } _%>
|
|
24
|
+
<%_ /* CGP-358b: Import cross-entity repos needed for belongs_to composition */ _%>
|
|
25
|
+
<%_ if (typeof clpBelongsTo !== 'undefined') { _%>
|
|
26
|
+
<%_ const uniqueBelongsToForModule = [...new Map(clpBelongsTo.filter(r => !r.isSelfFk).map(r => [r.relatedEntity, r])).values()]; _%>
|
|
27
|
+
<%_ uniqueBelongsToForModule.forEach(rel => { _%>
|
|
28
|
+
import { <%= rel.relatedEntityPascal %>Repository } from '../<%= rel.relatedPlural %>/<%= rel.relatedEntity %>.repository';
|
|
18
29
|
<%_ }) _%>
|
|
30
|
+
<%_ } _%>
|
|
19
31
|
<% if (eavEnabled) { -%>
|
|
20
32
|
import { FieldValuesModule } from '../field_values/field_values.module';
|
|
21
33
|
<% } -%>
|
|
@@ -68,6 +80,19 @@ import { <%= classNames.searchController %> } from './<%= entityName %>-search.c
|
|
|
68
80
|
providers: [
|
|
69
81
|
<%= classNames.repository %>,
|
|
70
82
|
<%= classNames.service %>,
|
|
83
|
+
<%_ /* CGP-358b: Register cross-entity repos as providers (needed for service DI) */ _%>
|
|
84
|
+
<%_ if (typeof clpExistingHasMany !== 'undefined') { _%>
|
|
85
|
+
<%_ const uniqueHasManyProviders = [...new Map(clpExistingHasMany.filter(r => !r.isSelfRef).map(r => [r.target, r])).values()]; _%>
|
|
86
|
+
<%_ uniqueHasManyProviders.forEach(rel => { _%>
|
|
87
|
+
<%= rel.targetClass %>Repository,
|
|
88
|
+
<%_ }) _%>
|
|
89
|
+
<%_ } _%>
|
|
90
|
+
<%_ if (typeof clpBelongsTo !== 'undefined') { _%>
|
|
91
|
+
<%_ const uniqueBelongsToProviders = [...new Map(clpBelongsTo.filter(r => !r.isSelfFk).map(r => [r.relatedEntity, r])).values()]; _%>
|
|
92
|
+
<%_ uniqueBelongsToProviders.forEach(rel => { _%>
|
|
93
|
+
<%= rel.relatedEntityPascal %>Repository,
|
|
94
|
+
<%_ }) _%>
|
|
95
|
+
<%_ } _%>
|
|
71
96
|
<%= classNames.findByIdUseCase %>,
|
|
72
97
|
<%= classNames.listUseCase %>,
|
|
73
98
|
<% if (eavEnabled) { -%>
|