@pattern-stack/codegen 0.6.8 → 0.7.1
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 +16 -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 +15 -2
- package/templates/entity/new/clean-lite-ps/module.ejs.t +27 -2
- package/templates/entity/new/clean-lite-ps/prompt-extension.js +72 -5
- 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-forwardref-clp-left.ejs.t +8 -0
- package/templates/junction/new/_inject-parent-module-forwardref-clp-right.ejs.t +8 -0
- package/templates/junction/new/_inject-parent-module-import-clp-left.ejs.t +8 -0
- package/templates/junction/new/_inject-parent-module-import-clp-right.ejs.t +8 -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-counterparty-clp-left.ejs.t +7 -0
- package/templates/junction/new/_inject-parent-service-counterparty-clp-right.ejs.t +7 -0
- package/templates/junction/new/_inject-parent-service-forwardref-clp-left.ejs.t +8 -0
- package/templates/junction/new/_inject-parent-service-forwardref-clp-right.ejs.t +8 -0
- package/templates/junction/new/_inject-parent-service-import-clp-left.ejs.t +9 -0
- package/templates/junction/new/_inject-parent-service-import-clp-right.ejs.t +9 -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
|
@@ -17,6 +17,22 @@ import { toEavRows, mergeEavRows } from '@shared/eav-helpers';
|
|
|
17
17
|
import type { DrizzleTx } from '@shared/types/drizzle';
|
|
18
18
|
import { <%= eavDefinitionPascal %>Repository } from '../<%= eavDefinitionEntityPlural %>/<%= eavDefinitionEntity %>.repository';
|
|
19
19
|
<% } -%>
|
|
20
|
+
<%_ /* CGP-358b — service-layer composition: import target repos for belongs_to relationships */ _%>
|
|
21
|
+
<%_ if (typeof clpBelongsTo !== 'undefined') { _%>
|
|
22
|
+
<%_ const uniqueBelongsToTargets = [...new Map(clpBelongsTo.filter(r => !r.isSelfFk).map(r => [r.relatedEntity, r])).values()]; _%>
|
|
23
|
+
<%_ uniqueBelongsToTargets.forEach(rel => { _%>
|
|
24
|
+
import { <%= rel.relatedEntityPascal %>Repository } from '../<%= rel.relatedPlural %>/<%= rel.relatedEntity %>.repository';
|
|
25
|
+
import type { <%= rel.relatedEntityPascal %> } from '../<%= rel.relatedPlural %>/<%= rel.relatedEntity %>.entity';
|
|
26
|
+
<%_ }) _%>
|
|
27
|
+
<%_ } _%>
|
|
28
|
+
<%_ /* CGP-358b — import target repos for has_many relationships */ _%>
|
|
29
|
+
<%_ if (typeof clpExistingHasMany !== 'undefined') { _%>
|
|
30
|
+
<%_ const uniqueHasManyTargets = [...new Map(clpExistingHasMany.filter(r => !r.isSelfRef).map(r => [r.target, r])).values()]; _%>
|
|
31
|
+
<%_ uniqueHasManyTargets.forEach(rel => { _%>
|
|
32
|
+
import { <%= rel.targetClass %>Repository } from '../<%= rel.targetPlural %>/<%= rel.target %>.repository';
|
|
33
|
+
import type { <%= rel.targetClass %> } from '../<%= rel.targetPlural %>/<%= rel.target %>.entity';
|
|
34
|
+
<%_ }) _%>
|
|
35
|
+
<%_ } _%>
|
|
20
36
|
|
|
21
37
|
@Injectable()
|
|
22
38
|
export class <%= classNames.service %> extends WithAnalytics(
|
|
@@ -43,6 +59,20 @@ export class <%= classNames.service %> extends WithAnalytics(
|
|
|
43
59
|
<% if (eavValueTable) { -%>
|
|
44
60
|
private readonly definitionRepo: <%= eavDefinitionPascal %>Repository,
|
|
45
61
|
<% } -%>
|
|
62
|
+
<%_ /* CGP-358b — inject target repos for belongs_to (non-self-ref) */ _%>
|
|
63
|
+
<%_ if (typeof clpBelongsTo !== 'undefined') { _%>
|
|
64
|
+
<%_ const uniqueBelongsToTargets2 = [...new Map(clpBelongsTo.filter(r => !r.isSelfFk).map(r => [r.relatedEntity, r])).values()]; _%>
|
|
65
|
+
<%_ uniqueBelongsToTargets2.forEach(rel => { _%>
|
|
66
|
+
private readonly <%= rel.relatedEntity.replace(/_([a-z])/g, (_, c) => c.toUpperCase()) %>Repo: <%= rel.relatedEntityPascal %>Repository,
|
|
67
|
+
<%_ }) _%>
|
|
68
|
+
<%_ } _%>
|
|
69
|
+
<%_ /* CGP-358b — inject target repos for has_many (non-self-ref) */ _%>
|
|
70
|
+
<%_ if (typeof clpExistingHasMany !== 'undefined') { _%>
|
|
71
|
+
<%_ const uniqueHasManyTargets2 = [...new Map(clpExistingHasMany.filter(r => !r.isSelfRef).map(r => [r.target, r])).values()]; _%>
|
|
72
|
+
<%_ uniqueHasManyTargets2.forEach(rel => { _%>
|
|
73
|
+
private readonly <%= rel.target.replace(/_([a-z])/g, (_, c) => c.toUpperCase()) %>Repo: <%= rel.targetClass %>Repository,
|
|
74
|
+
<%_ }) _%>
|
|
75
|
+
<%_ } _%>
|
|
46
76
|
) {
|
|
47
77
|
super(repository);
|
|
48
78
|
}
|
|
@@ -67,6 +97,55 @@ export class <%= classNames.service %> extends WithAnalytics(
|
|
|
67
97
|
}
|
|
68
98
|
<%_ }) _%>
|
|
69
99
|
<% } %>
|
|
100
|
+
<%_ /* CGP-358b — service-layer composition methods for relationships */ _%>
|
|
101
|
+
<%_ const hasBelongsToComposition = typeof clpBelongsTo !== 'undefined' && clpBelongsTo.length > 0; _%>
|
|
102
|
+
<%_ const hasHasManyComposition = typeof clpExistingHasMany !== 'undefined' && clpExistingHasMany.length > 0; _%>
|
|
103
|
+
<%_ if (hasBelongsToComposition || hasHasManyComposition) { _%>
|
|
104
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
105
|
+
// Relationship composition methods (CGP-358b / CGP-62)
|
|
106
|
+
// Two queries, no SQL JOIN. Core-contract path; relations() const stays
|
|
107
|
+
// as opt-in extension for hand-written Drizzle queries.
|
|
108
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
109
|
+
<%_ } _%>
|
|
110
|
+
<%_ if (hasBelongsToComposition) { _%>
|
|
111
|
+
<%_ clpBelongsTo.forEach(rel => { _%>
|
|
112
|
+
<%_ const relCamel = rel.relatedEntity.replace(/_([a-z])/g, (_, c) => c.toUpperCase()); _%>
|
|
113
|
+
<%_ const entityCamel = entityName.replace(/_([a-z])/g, (_, c) => c.toUpperCase()); _%>
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Fetch the <%= rel.relatedEntityPascal %> parent for this <%= entityNamePascal %>.
|
|
117
|
+
* Two repo calls: find self by id → find target by FK.
|
|
118
|
+
*/
|
|
119
|
+
async <%= rel.relationKey %>(<%- entityCamel %>Id: string): Promise<<%= rel.relatedEntityPascal %> | null> {
|
|
120
|
+
const entity = await this.repository.findById(<%- entityCamel %>Id);
|
|
121
|
+
if (!entity) return null;
|
|
122
|
+
<%_ if (rel.isSelfFk) { _%>
|
|
123
|
+
return entity.<%= rel.camelField %> ? this.repository.findById(entity.<%= rel.camelField %>) : null;
|
|
124
|
+
<%_ } else { _%>
|
|
125
|
+
return entity.<%= rel.camelField %> ? this.<%= relCamel %>Repo.findById(entity.<%= rel.camelField %>) : null;
|
|
126
|
+
<%_ } _%>
|
|
127
|
+
}
|
|
128
|
+
<%_ }) _%>
|
|
129
|
+
<%_ } _%>
|
|
130
|
+
<%_ if (hasHasManyComposition) { _%>
|
|
131
|
+
<%_ clpExistingHasMany.forEach(rel => { _%>
|
|
132
|
+
<%_ const relCamel = rel.target.replace(/_([a-z])/g, (_, c) => c.toUpperCase()); _%>
|
|
133
|
+
<%_ const entityCamel = entityName.replace(/_([a-z])/g, (_, c) => c.toUpperCase()); _%>
|
|
134
|
+
<%_ const fkPascal = rel.inverseForeignKeyPascal; _%>
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Fetch <%= rel.name %> for this <%= entityNamePascal %> by FK traversal.
|
|
138
|
+
* Single repo call with optional cursor/limit pagination.
|
|
139
|
+
*/
|
|
140
|
+
async <%= rel.name %>(<%- entityCamel %>Id: string, opts?: { cursor?: string; limit?: number }): Promise<<%= rel.targetClass %>[]> {
|
|
141
|
+
<%_ if (rel.isSelfRef) { _%>
|
|
142
|
+
return this.repository.findBy<%= fkPascal %>(<%- entityCamel %>Id, opts);
|
|
143
|
+
<%_ } else { _%>
|
|
144
|
+
return this.<%= relCamel %>Repo.findBy<%= fkPascal %>(<%- entityCamel %>Id, opts);
|
|
145
|
+
<%_ } _%>
|
|
146
|
+
}
|
|
147
|
+
<%_ }) _%>
|
|
148
|
+
<%_ } _%>
|
|
70
149
|
<% if (eavEnabled) { %>
|
|
71
150
|
/**
|
|
72
151
|
* EAV paired read (ADR-13): fetch the entity and merge dynamic `field_values`
|
|
@@ -568,6 +568,7 @@ export default {
|
|
|
568
568
|
moduleToGetByIdQuery: importHelpers.moduleToQuery(name, fileNames.getByIdQuery.replace('.ts', '')),
|
|
569
569
|
moduleToListQuery: importHelpers.moduleToQuery(name, fileNames.listQuery.replace('.ts', '')),
|
|
570
570
|
moduleToDeclarativeQueries: importHelpers.moduleToQuery(name, 'declarative-queries'),
|
|
571
|
+
moduleToRelationshipQueries: importHelpers.moduleToQuery(name, 'relationships.queries'),
|
|
571
572
|
moduleToCreateCommand: importHelpers.moduleToCommand(name, fileNames.createCommand.replace('.ts', '')),
|
|
572
573
|
moduleToUpdateCommand: importHelpers.moduleToCommand(name, fileNames.updateCommand.replace('.ts', '')),
|
|
573
574
|
moduleToDeleteCommand: importHelpers.moduleToCommand(name, fileNames.deleteCommand.replace('.ts', '')),
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: "<%= architecture === 'clean-lite-ps' && exposeOnParent.left ? parentModulePathLeft : '' %>"
|
|
3
|
+
inject: true
|
|
4
|
+
after: " DatabaseModule,"
|
|
5
|
+
skip_if: "<%= classNames.module %>"
|
|
6
|
+
---
|
|
7
|
+
// CGP-60 — junction module (forwardRef breaks the parent↔junction module cycle)
|
|
8
|
+
forwardRef(() => <%= classNames.module %>),
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: "<%= architecture === 'clean-lite-ps' && exposeOnParent.right ? parentModulePathRight : '' %>"
|
|
3
|
+
inject: true
|
|
4
|
+
after: " DatabaseModule,"
|
|
5
|
+
skip_if: "<%= classNames.module %>"
|
|
6
|
+
---
|
|
7
|
+
// CGP-60 — junction module (forwardRef breaks the parent↔junction module cycle)
|
|
8
|
+
forwardRef(() => <%= classNames.module %>),
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: "<%= architecture === 'clean-lite-ps' && exposeOnParent.left ? parentModulePathLeft : '' %>"
|
|
3
|
+
inject: true
|
|
4
|
+
after: "from '@nestjs/common';"
|
|
5
|
+
skip_if: "import { forwardRef"
|
|
6
|
+
---
|
|
7
|
+
// CGP-60 — forwardRef resolves parent↔junction module cycle
|
|
8
|
+
import { forwardRef } from '@nestjs/common';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: "<%= architecture === 'clean-lite-ps' && exposeOnParent.right ? parentModulePathRight : '' %>"
|
|
3
|
+
inject: true
|
|
4
|
+
after: "from '@nestjs/common';"
|
|
5
|
+
skip_if: "import { forwardRef"
|
|
6
|
+
---
|
|
7
|
+
// CGP-60 — forwardRef resolves parent↔junction module cycle
|
|
8
|
+
import { forwardRef } from '@nestjs/common';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: "<%= architecture === 'clean-lite-ps' && exposeOnParent.left ? parentModulePathLeft : '' %>"
|
|
3
|
+
inject: true
|
|
4
|
+
after: "from '@nestjs/common';"
|
|
5
|
+
skip_if: "<%= classNames.module %> }"
|
|
6
|
+
---
|
|
7
|
+
// CGP-60 — junction module wiring
|
|
8
|
+
import { <%= classNames.module %> } from '<%= junctionModuleImportFromLeft %>';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: "<%= architecture === 'clean-lite-ps' && exposeOnParent.right ? parentModulePathRight : '' %>"
|
|
3
|
+
inject: true
|
|
4
|
+
after: "from '@nestjs/common';"
|
|
5
|
+
skip_if: "<%= classNames.module %> }"
|
|
6
|
+
---
|
|
7
|
+
// CGP-60 — junction module wiring
|
|
8
|
+
import { <%= classNames.module %> } from '<%= junctionModuleImportFromRight %>';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: "<%= architecture === 'clean-lite-ps' && exposeOnParent.left ? parentServicePathLeft : '' %>"
|
|
3
|
+
inject: true
|
|
4
|
+
before: "// Inherited from"
|
|
5
|
+
skip_if: "<%= injectionMarkerLeft %>"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
9
|
+
// CGP-60 — fan-out to <%= rightEntityPascal %> (junction: <%= name %>)
|
|
10
|
+
// Delegates to <%= classNames.service %>. Per-junction marker keeps
|
|
11
|
+
// idempotency; multiple junctions on the same parent each emit their own
|
|
12
|
+
// block. `forwardRef` resolves the circular module import (parent module
|
|
13
|
+
// imports junction module; junction module imports parent modules for
|
|
14
|
+
// repo DI).
|
|
15
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
16
|
+
<%= injectionMarkerLeft %>
|
|
17
|
+
|
|
18
|
+
@Inject(forwardRef(() => <%= classNames.service %>))
|
|
19
|
+
private readonly <%= entityNameCamel %>Service!: <%= classNames.service %>;
|
|
20
|
+
|
|
21
|
+
async attach<%= rightEntityPascal %>(
|
|
22
|
+
<%= leftEntityCamel %>Id: string,
|
|
23
|
+
<%= rightEntityCamel %>Id: string,
|
|
24
|
+
link?: <%= entityNamePascal %>LinkInput,
|
|
25
|
+
): Promise<<%= entityNamePascal %>> {
|
|
26
|
+
return this.<%= entityNameCamel %>Service.attach(<%= leftEntityCamel %>Id, <%= rightEntityCamel %>Id, link);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async detach<%= rightEntityPascal %>(
|
|
30
|
+
<%= leftEntityCamel %>Id: string,
|
|
31
|
+
<%= rightEntityCamel %>Id: string,
|
|
32
|
+
): Promise<void> {
|
|
33
|
+
return this.<%= entityNameCamel %>Service.detach(<%= leftEntityCamel %>Id, <%= rightEntityCamel %>Id);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async <%= rightEntityPlural %>List(
|
|
37
|
+
<%= leftEntityCamel %>Id: string,
|
|
38
|
+
opts?: { cursor?: string; limit?: number },
|
|
39
|
+
): Promise<Array<{ entity: <%= rightEntityPascal %>; link: <%= entityNamePascal %> }>> {
|
|
40
|
+
return this.<%= entityNameCamel %>Service.listAssoc('left', <%= leftEntityCamel %>Id, opts) as Promise<
|
|
41
|
+
Array<{ entity: <%= rightEntityPascal %>; link: <%= entityNamePascal %> }>
|
|
42
|
+
>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async <%= rightEntityPlural %>SetPrimary(
|
|
46
|
+
<%= leftEntityCamel %>Id: string,
|
|
47
|
+
<%= rightEntityCamel %>Id: string,
|
|
48
|
+
): Promise<void> {
|
|
49
|
+
return this.<%= entityNameCamel %>Service.setPrimary(<%= leftEntityCamel %>Id, <%= rightEntityCamel %>Id);
|
|
50
|
+
}
|
|
51
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: "<%= architecture === 'clean-lite-ps' && exposeOnParent.right ? parentServicePathRight : '' %>"
|
|
3
|
+
inject: true
|
|
4
|
+
before: "// Inherited from"
|
|
5
|
+
skip_if: "<%= injectionMarkerRight %>"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
9
|
+
// CGP-60 — fan-out to <%= leftEntityPascal %> (junction: <%= name %>)
|
|
10
|
+
// Delegates to <%= classNames.service %>. See left-side block for the
|
|
11
|
+
// forwardRef + per-junction marker rationale.
|
|
12
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
13
|
+
<%= injectionMarkerRight %>
|
|
14
|
+
|
|
15
|
+
@Inject(forwardRef(() => <%= classNames.service %>))
|
|
16
|
+
private readonly <%= entityNameCamel %>Service!: <%= classNames.service %>;
|
|
17
|
+
|
|
18
|
+
async addTo<%= leftEntityPascal %>(
|
|
19
|
+
<%= rightEntityCamel %>Id: string,
|
|
20
|
+
<%= leftEntityCamel %>Id: string,
|
|
21
|
+
link?: <%= entityNamePascal %>LinkInput,
|
|
22
|
+
): Promise<<%= entityNamePascal %>> {
|
|
23
|
+
return this.<%= entityNameCamel %>Service.attach(<%= leftEntityCamel %>Id, <%= rightEntityCamel %>Id, link);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async removeFrom<%= leftEntityPascal %>(
|
|
27
|
+
<%= rightEntityCamel %>Id: string,
|
|
28
|
+
<%= leftEntityCamel %>Id: string,
|
|
29
|
+
): Promise<void> {
|
|
30
|
+
return this.<%= entityNameCamel %>Service.detach(<%= leftEntityCamel %>Id, <%= rightEntityCamel %>Id);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async <%= leftEntityPlural %>List(
|
|
34
|
+
<%= rightEntityCamel %>Id: string,
|
|
35
|
+
opts?: { cursor?: string; limit?: number },
|
|
36
|
+
): Promise<Array<{ entity: <%= leftEntityPascal %>; link: <%= entityNamePascal %> }>> {
|
|
37
|
+
return this.<%= entityNameCamel %>Service.listAssoc('right', <%= rightEntityCamel %>Id, opts) as Promise<
|
|
38
|
+
Array<{ entity: <%= leftEntityPascal %>; link: <%= entityNamePascal %> }>
|
|
39
|
+
>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async <%= leftEntityPlural %>SetPrimary(
|
|
43
|
+
<%= rightEntityCamel %>Id: string,
|
|
44
|
+
<%= leftEntityCamel %>Id: string,
|
|
45
|
+
): Promise<void> {
|
|
46
|
+
return this.<%= entityNameCamel %>Service.setPrimary(<%= leftEntityCamel %>Id, <%= rightEntityCamel %>Id);
|
|
47
|
+
}
|
|
48
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: "<%= architecture === 'clean-lite-ps' && exposeOnParent.left ? parentServicePathLeft : '' %>"
|
|
3
|
+
inject: true
|
|
4
|
+
after: "from '@nestjs/common';"
|
|
5
|
+
skip_if: "from '<%= rightEntityImportFromJunction %>'"
|
|
6
|
+
---
|
|
7
|
+
import type { <%= rightEntityPascal %> } from '<%= rightEntityImportFromJunction %>';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: "<%= architecture === 'clean-lite-ps' && exposeOnParent.right ? parentServicePathRight : '' %>"
|
|
3
|
+
inject: true
|
|
4
|
+
after: "from '@nestjs/common';"
|
|
5
|
+
skip_if: "from '<%= leftEntityImportFromJunction %>'"
|
|
6
|
+
---
|
|
7
|
+
import type { <%= leftEntityPascal %> } from '<%= leftEntityImportFromJunction %>';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: "<%= architecture === 'clean-lite-ps' && exposeOnParent.left ? parentServicePathLeft : '' %>"
|
|
3
|
+
inject: true
|
|
4
|
+
after: "from '@nestjs/common';"
|
|
5
|
+
skip_if: "import { forwardRef"
|
|
6
|
+
---
|
|
7
|
+
// CGP-60 — forwardRef resolves circular module dep (junction → parent)
|
|
8
|
+
import { forwardRef } from '@nestjs/common';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: "<%= architecture === 'clean-lite-ps' && exposeOnParent.right ? parentServicePathRight : '' %>"
|
|
3
|
+
inject: true
|
|
4
|
+
after: "from '@nestjs/common';"
|
|
5
|
+
skip_if: "import { forwardRef"
|
|
6
|
+
---
|
|
7
|
+
// CGP-60 — forwardRef resolves circular module dep (junction → parent)
|
|
8
|
+
import { forwardRef } from '@nestjs/common';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: "<%= architecture === 'clean-lite-ps' && exposeOnParent.left ? parentServicePathLeft : '' %>"
|
|
3
|
+
inject: true
|
|
4
|
+
after: "from '@nestjs/common';"
|
|
5
|
+
skip_if: "<%= classNames.service %> }"
|
|
6
|
+
---
|
|
7
|
+
// CGP-60 — junction service + types
|
|
8
|
+
import { <%= classNames.service %>, <%= entityNamePascal %>LinkInput } from '<%= junctionServiceImportFromLeft %>';
|
|
9
|
+
import type { <%= entityNamePascal %> } from '../<%= entityNamePlural %>/<%= name %>.entity';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: "<%= architecture === 'clean-lite-ps' && exposeOnParent.right ? parentServicePathRight : '' %>"
|
|
3
|
+
inject: true
|
|
4
|
+
after: "from '@nestjs/common';"
|
|
5
|
+
skip_if: "<%= classNames.service %> }"
|
|
6
|
+
---
|
|
7
|
+
// CGP-60 — junction service + types
|
|
8
|
+
import { <%= classNames.service %>, <%= entityNamePascal %>LinkInput } from '<%= junctionServiceImportFromRight %>';
|
|
9
|
+
import type { <%= entityNamePascal %> } from '../<%= entityNamePlural %>/<%= name %>.entity';
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: "<%= outputPaths.entity %>"
|
|
3
|
+
force: true
|
|
4
|
+
---
|
|
5
|
+
import {
|
|
6
|
+
<%_ drizzleImports.filter(i => i !== 'relations').forEach(i => { _%>
|
|
7
|
+
<%= i %>,
|
|
8
|
+
<%_ }) _%>
|
|
9
|
+
} from 'drizzle-orm/pg-core';
|
|
10
|
+
import { relations, type InferSelectModel } from 'drizzle-orm';
|
|
11
|
+
import { <%= leftTable %> } from '../<%= leftTable %>/<%= leftEntity %>.entity';
|
|
12
|
+
<%_ if (leftEntity !== rightEntity) { _%>
|
|
13
|
+
import { <%= rightTable %> } from '../<%= rightTable %>/<%= rightEntity %>.entity';
|
|
14
|
+
<%_ } _%>
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Enums
|
|
18
|
+
// ============================================================================
|
|
19
|
+
<%_ if (hasRole) { _%>
|
|
20
|
+
|
|
21
|
+
export const <%= roleEnumName %> = pgEnum('<%= name %>_role', [
|
|
22
|
+
<%_ roleEnumValues.forEach(v => { _%>
|
|
23
|
+
'<%= v %>',
|
|
24
|
+
<%_ }) _%>
|
|
25
|
+
]);
|
|
26
|
+
<%_ } _%>
|
|
27
|
+
<%_ processedCustomFields.filter(f => f.hasChoices).forEach(field => { _%>
|
|
28
|
+
|
|
29
|
+
export const <%= field.enumName %> = pgEnum('<%= name %>_<%= field.name %>', [
|
|
30
|
+
<%_ field.choices.forEach(c => { _%>
|
|
31
|
+
'<%= c %>',
|
|
32
|
+
<%_ }) _%>
|
|
33
|
+
]);
|
|
34
|
+
<%_ }) _%>
|
|
35
|
+
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// Table
|
|
38
|
+
// ============================================================================
|
|
39
|
+
|
|
40
|
+
export const <%= tableVarName %> = pgTable(
|
|
41
|
+
'<%= tableName %>',
|
|
42
|
+
{
|
|
43
|
+
// FK columns — composite primary key (no surrogate id: Q4 resolution)
|
|
44
|
+
<%= leftColumnCamel %>: uuid('<%= leftColumn %>').notNull().references(() => <%= leftTable %>.id, { onDelete: '<%= onDeleteLeft %>' }),
|
|
45
|
+
<%= rightColumnCamel %>: uuid('<%= rightColumn %>').notNull().references(() => <%= rightTable %>.id, { onDelete: '<%= onDeleteRight %>' }),
|
|
46
|
+
<%_ if (hasRole) { _%>
|
|
47
|
+
|
|
48
|
+
// Role enum (per-pairing; declared in junction YAML's fields.role.choices)
|
|
49
|
+
role: <%= roleEnumName %>('role'),
|
|
50
|
+
<%_ } _%>
|
|
51
|
+
|
|
52
|
+
// BaseJunctionFields — is_primary is always emitted
|
|
53
|
+
isPrimary: boolean('is_primary').notNull().default(false),
|
|
54
|
+
<%_ if (temporal) { _%>
|
|
55
|
+
|
|
56
|
+
// Temporal window (temporal: true, default)
|
|
57
|
+
startedAt: timestamp('started_at'),
|
|
58
|
+
endedAt: timestamp('ended_at'),
|
|
59
|
+
<%_ } _%>
|
|
60
|
+
<%_ if (sourced) { _%>
|
|
61
|
+
|
|
62
|
+
// Provenance (sourced: true, default)
|
|
63
|
+
sourcedFrom: text('sourced_from'),
|
|
64
|
+
confidence: numeric('confidence', { precision: 5, scale: 4 }),
|
|
65
|
+
matchedAt: timestamp('matched_at'),
|
|
66
|
+
<%_ } _%>
|
|
67
|
+
<%_ if (hasCustomFields) { _%>
|
|
68
|
+
|
|
69
|
+
// Custom fields
|
|
70
|
+
<%_ processedCustomFields.forEach(field => { _%>
|
|
71
|
+
<%_ if (field.hasChoices) { _%>
|
|
72
|
+
<%= field.camelName %>: <%= field.enumName %>('<%= field.name %>'),
|
|
73
|
+
<%_ } else if (field.drizzleType === 'uuid') { _%>
|
|
74
|
+
<%= field.camelName %>: uuid('<%= field.name %>'),
|
|
75
|
+
<%_ } else { _%>
|
|
76
|
+
<%= field.camelName %>: <%= field.drizzleType %>('<%= field.name %>'),
|
|
77
|
+
<%_ } _%>
|
|
78
|
+
<%_ }) _%>
|
|
79
|
+
<%_ } _%>
|
|
80
|
+
|
|
81
|
+
// Timestamps
|
|
82
|
+
createdAt: timestamp('created_at').notNull().defaultNow(),
|
|
83
|
+
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
|
84
|
+
},
|
|
85
|
+
(table) => [
|
|
86
|
+
// Composite primary key on the two FK columns (Q4 resolution: no surrogate id)
|
|
87
|
+
primaryKey({ columns: [table.<%= leftColumnCamel %>, table.<%= rightColumnCamel %>] }),
|
|
88
|
+
],
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
export type <%= classNames.entity %> = InferSelectModel<typeof <%= tableVarName %>>;
|
|
92
|
+
export type <%= classNames.entity %>Insert = typeof <%= tableVarName %>.$inferInsert;
|
|
93
|
+
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// Relations — extension-path metadata for db.query.X.findMany({ with: ... })
|
|
96
|
+
// Generated code does NOT consume these; they exist for hand-written admin
|
|
97
|
+
// queries and for #60's fan-out methods once they land.
|
|
98
|
+
// ============================================================================
|
|
99
|
+
|
|
100
|
+
export const <%= tableVarName %>Relations = relations(<%= tableVarName %>, ({ one }) => ({
|
|
101
|
+
<%= leftEntity %>: one(<%= leftTable %>, {
|
|
102
|
+
fields: [<%= tableVarName %>.<%= leftColumnCamel %>],
|
|
103
|
+
references: [<%= leftTable %>.id],
|
|
104
|
+
}),
|
|
105
|
+
<%_ if (leftEntity !== rightEntity) { _%>
|
|
106
|
+
<%= rightEntity %>: one(<%= rightTable %>, {
|
|
107
|
+
fields: [<%= tableVarName %>.<%= rightColumnCamel %>],
|
|
108
|
+
references: [<%= rightTable %>.id],
|
|
109
|
+
}),
|
|
110
|
+
<%_ } _%>
|
|
111
|
+
}));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: "<%= outputPaths.index %>"
|
|
3
|
+
force: true
|
|
4
|
+
---
|
|
5
|
+
/**
|
|
6
|
+
* <%= classNames.entity %> module barrel export
|
|
7
|
+
* Generated by junction codegen — do not edit directly
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Value exports (module, service)
|
|
11
|
+
export { <%= classNames.module %> } from './<%= entityNamePlural %>.module';
|
|
12
|
+
export { <%= classNames.service %> } from './<%= name %>.service';
|
|
13
|
+
|
|
14
|
+
// Type-only exports (entity)
|
|
15
|
+
export type { <%= classNames.entity %> } from './<%= name %>.entity';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: "<%= outputPaths.module %>"
|
|
3
|
+
force: true
|
|
4
|
+
---
|
|
5
|
+
import { Module, forwardRef } from '@nestjs/common';
|
|
6
|
+
import { DatabaseModule } from '@shared/database/database.module';
|
|
7
|
+
|
|
8
|
+
import { <%= classNames.repository %> } from './<%= name %>.repository';
|
|
9
|
+
import { <%= classNames.service %> } from './<%= name %>.service';
|
|
10
|
+
import { <%= leftModuleClass %> } from '<%= leftModuleImportFromJunction %>';
|
|
11
|
+
import { <%= rightModuleClass %> } from '<%= rightModuleImportFromJunction %>';
|
|
12
|
+
|
|
13
|
+
// Note: No controller — junctions are not directly addressable HTTP resources
|
|
14
|
+
// in v1 (Q1 resolution). They are accessed via the canonical port of one of
|
|
15
|
+
// the two parent entities. Add a controller in a follow-up if a consumer
|
|
16
|
+
// surfaces a need for direct HTTP access to junction rows.
|
|
17
|
+
|
|
18
|
+
@Module({
|
|
19
|
+
imports: [
|
|
20
|
+
DatabaseModule,
|
|
21
|
+
// CGP-60 — parent modules provide the left/right repositories that the
|
|
22
|
+
// junction service injects for the canonical `list` composition path.
|
|
23
|
+
// forwardRef resolves the parent↔junction module cycle (parent modules
|
|
24
|
+
// also import this module to wire fan-out).
|
|
25
|
+
forwardRef(() => <%= leftModuleClass %>),
|
|
26
|
+
forwardRef(() => <%= rightModuleClass %>),
|
|
27
|
+
// TODO: Add subsystem modules as needed (EventsSubsystemModule, etc.)
|
|
28
|
+
],
|
|
29
|
+
controllers: [],
|
|
30
|
+
providers: [
|
|
31
|
+
<%= classNames.repository %>,
|
|
32
|
+
<%= classNames.service %>,
|
|
33
|
+
// TODO: Register hand-written use cases here
|
|
34
|
+
],
|
|
35
|
+
exports: [<%= classNames.service %>], // Only service is exported (ADR-002)
|
|
36
|
+
})
|
|
37
|
+
export class <%= classNames.module %> {}
|