@pattern-stack/codegen 0.6.0 → 0.6.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.
@@ -0,0 +1,32 @@
1
+ /**
2
+ * ActivityPattern — replaces `family: activity`.
3
+ *
4
+ * Activity entities represent time-bounded interactions (calls, meetings,
5
+ * emails). The base repository/service expose date-range + opportunity +
6
+ * user-scoped lookups on top of the standard CRUD methods.
7
+ *
8
+ * Class names, import paths, and inherited-method strings match the
9
+ * legacy `FAMILY_MAP` entry verbatim so PATTERN-5's template swap produces
10
+ * byte-identical output.
11
+ */
12
+
13
+ import { definePattern } from '../pattern-definition.js';
14
+
15
+ export const ActivityPattern = definePattern({
16
+ name: 'Activity',
17
+ extends: ['Base'],
18
+ repositoryClass: 'ActivityEntityRepository',
19
+ serviceClass: 'ActivityEntityService',
20
+ repositoryImport: '@shared/base-classes/activity-entity-repository',
21
+ serviceImport: '@shared/base-classes/activity-entity-service',
22
+ repositoryInheritedMethods: [
23
+ 'findById, findByIds, list, count, exists, create, update, delete, upsertMany',
24
+ 'findByDateRange, findByUserId, findByOpportunityId, findRecentByOpportunityId',
25
+ ],
26
+ serviceInheritedMethods: [
27
+ 'findById, findByIds, list, count, exists, create, update, delete',
28
+ 'findByDateRange, findByUserId, findByOpportunityId, findRecentByOpportunityId',
29
+ ],
30
+ description:
31
+ 'Time-bounded interaction entities — date-range + opportunity scoped lookups',
32
+ });
@@ -0,0 +1,28 @@
1
+ /**
2
+ * BasePattern — identity pattern for the `extends` chain.
3
+ *
4
+ * Contributes no columns, no implied behaviors, and no config. Its only
5
+ * purpose is to anchor the inheritance hierarchy so every other pattern
6
+ * can declare `extends: ['Base']` and codegen can resolve that to a
7
+ * concrete `BaseRepository` / `BaseService` reference.
8
+ *
9
+ * Matches the existing `family: base` entry in
10
+ * `templates/entity/new/clean-lite-ps/prompt-extension.js` verbatim.
11
+ */
12
+
13
+ import { definePattern } from '../pattern-definition.js';
14
+
15
+ export const BasePattern = definePattern({
16
+ name: 'Base',
17
+ repositoryClass: 'BaseRepository',
18
+ serviceClass: 'BaseService',
19
+ repositoryImport: '@shared/base-classes/base-repository',
20
+ serviceImport: '@shared/base-classes/base-service',
21
+ repositoryInheritedMethods: [
22
+ 'findById, findByIds, list, count, exists, create, update, delete, upsertMany',
23
+ ],
24
+ serviceInheritedMethods: [
25
+ 'findById, findByIds, list, count, exists, create, update, delete',
26
+ ],
27
+ description: 'Identity pattern — base CRUD, no extra columns or methods',
28
+ });
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Library pattern bootstrap — imports every shipped pattern and registers
3
+ * it with the shared library registry. Side-effect-only module: importing
4
+ * this barrel is what pre-registers `Base`, `Synced`, `Activity`,
5
+ * `Knowledge`, and `Metadata`.
6
+ *
7
+ * Adding a new library pattern is two edits: create the `*.pattern.ts`
8
+ * file and add the import+register pair below.
9
+ */
10
+
11
+ import { registerLibraryPattern } from '../registry.js';
12
+ import { ActivityPattern } from './activity.pattern.js';
13
+ import { BasePattern } from './base.pattern.js';
14
+ import { KnowledgePattern } from './knowledge.pattern.js';
15
+ import { MetadataPattern } from './metadata.pattern.js';
16
+ import { SyncedPattern } from './synced.pattern.js';
17
+
18
+ registerLibraryPattern(BasePattern);
19
+ registerLibraryPattern(SyncedPattern);
20
+ registerLibraryPattern(ActivityPattern);
21
+ registerLibraryPattern(KnowledgePattern);
22
+ registerLibraryPattern(MetadataPattern);
23
+
24
+ export {
25
+ ActivityPattern,
26
+ BasePattern,
27
+ KnowledgePattern,
28
+ MetadataPattern,
29
+ SyncedPattern,
30
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * KnowledgePattern — replaces `family: knowledge`.
3
+ *
4
+ * Knowledge entities hold long-form content with a workflow status and
5
+ * semantic-search support (vectors, pending/approved states). The base
6
+ * classes expose `semanticSearch`, pending-by-opportunity lookups, and
7
+ * batch status updates.
8
+ *
9
+ * Class names, import paths, and inherited-method strings match the
10
+ * legacy `FAMILY_MAP` entry verbatim.
11
+ */
12
+
13
+ import { definePattern } from '../pattern-definition.js';
14
+
15
+ export const KnowledgePattern = definePattern({
16
+ name: 'Knowledge',
17
+ extends: ['Base'],
18
+ repositoryClass: 'KnowledgeEntityRepository',
19
+ serviceClass: 'KnowledgeEntityService',
20
+ repositoryImport: '@shared/base-classes/knowledge-entity-repository',
21
+ serviceImport: '@shared/base-classes/knowledge-entity-service',
22
+ repositoryInheritedMethods: [
23
+ 'findById, findByIds, list, count, exists, create, update, delete, upsertMany',
24
+ 'semanticSearch, findPendingByOpportunityId, updateStatus, updateStatusBatch',
25
+ ],
26
+ serviceInheritedMethods: [
27
+ 'findById, findByIds, list, count, exists, create, update, delete',
28
+ 'semanticSearch, findPendingByOpportunityId, updateStatus, updateStatusBatch',
29
+ ],
30
+ description: 'Knowledge entities — semantic search + workflow status',
31
+ });
@@ -0,0 +1,31 @@
1
+ /**
2
+ * MetadataPattern — replaces `family: metadata`.
3
+ *
4
+ * Metadata entities represent history-tracked auxiliary rows attached to a
5
+ * parent entity (audit trails, custom-field values, change logs). The base
6
+ * classes expose entity-id + type scoped lookups and history listing.
7
+ *
8
+ * Class names, import paths, and inherited-method strings match the
9
+ * legacy `FAMILY_MAP` entry verbatim.
10
+ */
11
+
12
+ import { definePattern } from '../pattern-definition.js';
13
+
14
+ export const MetadataPattern = definePattern({
15
+ name: 'Metadata',
16
+ extends: ['Base'],
17
+ repositoryClass: 'MetadataEntityRepository',
18
+ serviceClass: 'MetadataEntityService',
19
+ repositoryImport: '@shared/base-classes/metadata-entity-repository',
20
+ serviceImport: '@shared/base-classes/metadata-entity-service',
21
+ repositoryInheritedMethods: [
22
+ 'findById, findByIds, list, count, exists, create, update, delete, upsertMany',
23
+ 'findByEntityIdAndType, listByEntityId, listHistoryByEntityId',
24
+ ],
25
+ serviceInheritedMethods: [
26
+ 'findById, findByIds, list, count, exists, create, update, delete',
27
+ 'findByEntityIdAndType, listByEntityId, listHistoryByEntityId',
28
+ ],
29
+ description:
30
+ 'History-tracked metadata rows — entity-id + type scoped lookups',
31
+ });
@@ -0,0 +1,34 @@
1
+ /**
2
+ * SyncedPattern — adds external-system sync columns and methods.
3
+ *
4
+ * Replaces the legacy `family: synced` entry in
5
+ * `templates/entity/new/clean-lite-ps/prompt-extension.js`. Class names,
6
+ * import paths, and inherited-method comment lines are preserved verbatim
7
+ * so PATTERN-5's template swap produces byte-identical output for
8
+ * pre-existing `family: synced` fixtures.
9
+ *
10
+ * Implies `external_id_tracking` — the behavior that contributes the
11
+ * `external_id`, `provider`, and `provider_metadata` columns to the table.
12
+ * An entity declaring `pattern: Synced` need not re-declare the behavior.
13
+ */
14
+
15
+ import { definePattern } from '../pattern-definition.js';
16
+
17
+ export const SyncedPattern = definePattern({
18
+ name: 'Synced',
19
+ extends: ['Base'],
20
+ repositoryClass: 'SyncedEntityRepository',
21
+ serviceClass: 'SyncedEntityService',
22
+ repositoryImport: '@shared/base-classes/synced-entity-repository',
23
+ serviceImport: '@shared/base-classes/synced-entity-service',
24
+ repositoryInheritedMethods: [
25
+ 'findById, findByIds, list, count, exists, create, update, delete, upsertMany',
26
+ 'findByExternalId, findAllByUserId, findVisibleByUserId, syncUpsert',
27
+ ],
28
+ serviceInheritedMethods: [
29
+ 'findById, findByIds, list, count, exists, create, update, delete',
30
+ 'findByExternalId, findAllByUserId, findVisibleByUserId',
31
+ ],
32
+ impliedBehaviors: ['external_id_tracking'],
33
+ description: 'External CRM/system sync columns and syncUpsert methods',
34
+ });
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Pattern Definition — pure metadata record returned by an identity function.
3
+ *
4
+ * `definePattern()` is the registration artifact for both library-shipped and
5
+ * app-defined patterns. It carries only names + import paths for the classes
6
+ * a generated entity should extend — never the class constructors themselves.
7
+ * This keeps the codegen pipeline free of TS class-evaluation cost and avoids
8
+ * `reflect-metadata`, which lets the Hygen subprocess cheaply rebuild the
9
+ * registry (see `src/cli/shared/hygen.ts` — the registry loads twice per
10
+ * `entity new` invocation).
11
+ *
12
+ * Two pattern kinds share this surface:
13
+ * - **domain** (default; ADR-031) — `PatternDefinition`. Contributes
14
+ * repository/service base classes, columns, behaviors to entities that
15
+ * declare `pattern:`/`patterns:` in YAML.
16
+ * - **orchestration** (ADR-032) — `OrchestrationPatternDefinition`. Declares
17
+ * a DI registry + optional dispatcher scaffold. Not entity-attached;
18
+ * codegen emits a NestJS module under `src/orchestration/` instead.
19
+ *
20
+ * See `docs/adrs/ADR-031-app-defined-patterns.md` §"Decision 1" for the
21
+ * domain binding surface and `docs/adrs/ADR-032-orchestration-patterns.md`
22
+ * for the orchestration kind.
23
+ */
24
+
25
+ import type { ZodSchema } from 'zod';
26
+
27
+ /**
28
+ * A column a pattern contributes to every entity that declares it.
29
+ *
30
+ * Column-level conflicts between patterns, between a pattern and an
31
+ * entity-declared field, or between a pattern and a behavior-contributed
32
+ * field are codegen-time hard errors; see
33
+ * `src/patterns/validate-composition.ts`.
34
+ */
35
+ export interface PatternColumnContribution {
36
+ /** snake_case column name — matches the database column */
37
+ name: string;
38
+ /** Drizzle column type string, e.g. "varchar(255)" or "text" */
39
+ type: string;
40
+ }
41
+
42
+ /**
43
+ * Discriminator for the two pattern shapes. Default is `"domain"` to preserve
44
+ * Phase 1 (ADR-031) behaviour — every existing PatternDefinition without a
45
+ * `kind` field continues to register as a domain pattern.
46
+ */
47
+ export type PatternKind = 'domain' | 'orchestration';
48
+
49
+ /**
50
+ * The full pattern metadata record. Every `definePattern({...})` call
51
+ * returns a value of this shape; the library and consumer registries
52
+ * store these and look them up by `name`.
53
+ */
54
+ export interface PatternDefinition<TConfig = unknown> {
55
+ /** Unique name used in YAML — e.g. `pattern: Synced` */
56
+ name: string;
57
+
58
+ /**
59
+ * ADR-032: defaults to `"domain"`. Phase 3 adds `"orchestration"` as a
60
+ * disjoint shape (see `OrchestrationPatternDefinition`). Domain
61
+ * `PatternDefinition` instances must omit this field or set it to
62
+ * `"domain"`; the loader routes orchestration values to a separate map.
63
+ */
64
+ kind?: 'domain';
65
+
66
+ /**
67
+ * Built-in patterns this extends, by name. Phase 1 supports single-depth
68
+ * chains only — a pattern may `extends: ['Synced']` but the transitive
69
+ * chain is not yet resolved. Multi-depth inheritance is deferred until
70
+ * a real consumer asks.
71
+ */
72
+ extends?: string[];
73
+
74
+ /** Constructor name codegen emits in the generated repo's `extends` clause */
75
+ repositoryClass?: string;
76
+ /** Constructor name codegen emits in the generated service's `extends` clause */
77
+ serviceClass?: string;
78
+
79
+ /**
80
+ * Fully-qualified TypeScript path alias the consumer's tsconfig resolves.
81
+ * Library patterns use the consumer-installed runtime base class path
82
+ * (e.g. `@shared/base-classes/synced-entity-repository`); app patterns
83
+ * use whatever alias the consumer has configured (e.g. `@/patterns/...`).
84
+ */
85
+ repositoryImport?: string;
86
+ /** Same as `repositoryImport` but for the service base class */
87
+ serviceImport?: string;
88
+
89
+ /**
90
+ * Documentation-only method-signature strings emitted as comments in the
91
+ * generated repo. Exist purely so app authors reading the generated file
92
+ * see what their concrete class inherits without jumping to the base.
93
+ */
94
+ repositoryInheritedMethods?: string[];
95
+ /** Same as `repositoryInheritedMethods` but for the service base class */
96
+ serviceInheritedMethods?: string[];
97
+
98
+ /**
99
+ * Columns this pattern adds to every entity that declares it. Used by
100
+ * the composition validator to detect column-name collisions.
101
+ */
102
+ columns?: PatternColumnContribution[];
103
+
104
+ /**
105
+ * Behaviors this pattern implicitly enables. Entity YAML need not
106
+ * re-declare them; duplicates across patterns are silent-deduped.
107
+ */
108
+ impliedBehaviors?: string[];
109
+
110
+ /**
111
+ * Zod schema that validates the per-entity `config:` block for this
112
+ * pattern at parse time. When absent, entities may not supply a `config:`
113
+ * entry for this pattern and codegen emits no `patternConfig` property.
114
+ */
115
+ configSchema?: ZodSchema<TConfig>;
116
+
117
+ /** One-line description for codegen help output and error messages */
118
+ description?: string;
119
+ }
120
+
121
+ /**
122
+ * Identity function that returns its argument unchanged. The body is trivial
123
+ * on purpose — the whole point is to give TypeScript a hook for generic
124
+ * inference on `TConfig` while leaving the runtime value a plain object
125
+ * registered by the codegen loader.
126
+ */
127
+ export function definePattern<TConfig = unknown>(
128
+ def: PatternDefinition<TConfig>,
129
+ ): PatternDefinition<TConfig> {
130
+ return def;
131
+ }
132
+
133
+ /**
134
+ * Shape check for values produced by `import()`ing an app pattern file.
135
+ * The registry loader runs this on every exported value it finds; only
136
+ * values that pass are registered.
137
+ *
138
+ * We keep this deliberately loose — a `name` string is the whole
139
+ * requirement — because a pattern that contributes neither columns nor
140
+ * class references is still a *valid* identity pattern (e.g. `BasePattern`
141
+ * exists to anchor the `extends` chain without contributing anything).
142
+ * Stricter shape rules belong in the registry's "at-least-one-contribution"
143
+ * check, not here.
144
+ *
145
+ * This function is intentionally **kind-agnostic** — both
146
+ * `PatternDefinition` (domain) and `OrchestrationPatternDefinition`
147
+ * (orchestration) pass. The discriminator routing happens in the loader
148
+ * via `isOrchestrationPattern()`/`isDomainPattern()`.
149
+ */
150
+ export function isPatternDefinition(val: unknown): val is PatternDefinition {
151
+ return (
152
+ typeof val === 'object' &&
153
+ val !== null &&
154
+ 'name' in val &&
155
+ typeof (val as { name: unknown }).name === 'string'
156
+ );
157
+ }
158
+
159
+ // ============================================================================
160
+ // Orchestration kind (ADR-032)
161
+ // ============================================================================
162
+
163
+ /**
164
+ * One registry's declarative shape. ADR-032 §"The Proposal".
165
+ *
166
+ * Phase 3-1 records this; Phase 3-2 codegen reads it to emit token files,
167
+ * provider blocks, and dispatcher overload signatures. Phase 3-1 validates
168
+ * only what is statically checkable from this record alone — see
169
+ * `validate-orchestration.ts` for the rules and their deferral notes.
170
+ */
171
+ export interface OrchestrationRegistrySpec {
172
+ /**
173
+ * Identifier for co-keyed sibling registries (ADR-032 Phase 3-2/3, O-1).
174
+ *
175
+ * The PRIMARY registry never carries a `name` — its tokens / methods are
176
+ * derived from the pattern name alone (`${PATTERN_CONST}_REGISTRY`,
177
+ * `select(...)`). Each co-keyed sibling MUST carry an explicit `name`
178
+ * which the emitter uppercases for the token suffix and PascalCases for
179
+ * the dispatcher method suffix:
180
+ *
181
+ * `coKeyedRegistries: [{ name: 'auth', valueType: 'IAuthStrategy' ... }]`
182
+ * ⇒ `CRM_PORTS_AUTH_REGISTRY` token + `selectAuth(...)` method.
183
+ *
184
+ * No auto-stripping of "I" prefix or "Strategy/Port/Adapter/Provider"
185
+ * suffixes — authors pick what reads right.
186
+ */
187
+ name?: string;
188
+ /**
189
+ * Type alias the consumer's tsconfig resolves (e.g. `"CrmAdapterDomain"`).
190
+ * Phase 3-1 stores this string verbatim. Resolution that the path actually
191
+ * imports a concrete TS enum is deferred to Phase 3-2 emission, where
192
+ * codegen will need to read the consumer's source tree.
193
+ */
194
+ keyType: string;
195
+ /**
196
+ * Module specifier the emitter writes into `import type { keyType } from
197
+ * '<keyTypeImport>'`. Required at Phase 3-2 emission; the generator emits
198
+ * `pattern_missing_import_path` if absent. (ADR-032 Phase 3-2 §3.4 / O-3.)
199
+ */
200
+ keyTypeImport?: string;
201
+ /** Same shape as `keyType` — the registry's value-type interface ref. */
202
+ valueType: string;
203
+ /** Module specifier for `valueType` import. See `keyTypeImport`. */
204
+ valueTypeImport?: string;
205
+ entries: ReadonlyArray<{
206
+ /** Stable string key — must be unique within this registry. */
207
+ key: string;
208
+ /**
209
+ * Concrete provider class name (NOT a DI token string). Codegen will
210
+ * import this and use it as the constructor injectable.
211
+ * Phase 3-1 records it; Phase 3-2 verifies it resolves.
212
+ */
213
+ provider: string;
214
+ /** Module specifier for `provider` import. See `keyTypeImport`. */
215
+ providerImport?: string;
216
+ }>;
217
+ }
218
+
219
+ /**
220
+ * Orchestration pattern — declarative DI registry + optional dispatcher
221
+ * scaffold. ADR-032 §"The Proposal" + Decisions 1-8.
222
+ *
223
+ * Disjoint from `PatternDefinition` (domain): no columns, no
224
+ * repository/service base class, no entity-level patternConfig. Composition
225
+ * with domain patterns happens only at the DI layer in the consumer's
226
+ * generated code, not in entity YAML.
227
+ */
228
+ export interface OrchestrationPatternDefinition {
229
+ name: string;
230
+ kind: 'orchestration';
231
+ /** Primary registry (always present). */
232
+ registry: OrchestrationRegistrySpec;
233
+ /**
234
+ * Sibling registries that share the primary registry's key space.
235
+ * ADR-032 Decision 2 — co-keyed groups are a first-class field.
236
+ * Validator enforces matching `keyType` across the group.
237
+ */
238
+ coKeyedRegistries?: ReadonlyArray<OrchestrationRegistrySpec>;
239
+ /** Optional dispatcher scaffold spec (ADR-032 Decision 4 + 5). */
240
+ dispatcher?: {
241
+ /** Class name to emit (e.g. `"CrmPortsDispatcher"`). */
242
+ className: string;
243
+ /**
244
+ * Method name the consumer overrides in their subclass to fill the
245
+ * assembly body (ADR-032 Decision 5).
246
+ */
247
+ assemblySlot: string;
248
+ };
249
+ /** One-line description for help output and error messages. */
250
+ description?: string;
251
+ }
252
+
253
+ /** Union for callers that need to handle both shapes. */
254
+ export type AnyPatternDefinition =
255
+ | PatternDefinition
256
+ | OrchestrationPatternDefinition;
257
+
258
+ export function isOrchestrationPattern(
259
+ def: AnyPatternDefinition,
260
+ ): def is OrchestrationPatternDefinition {
261
+ return (def as { kind?: PatternKind }).kind === 'orchestration';
262
+ }
263
+
264
+ export function isDomainPattern(
265
+ def: AnyPatternDefinition,
266
+ ): def is PatternDefinition {
267
+ return !isOrchestrationPattern(def);
268
+ }
269
+
270
+ /**
271
+ * Identity function that returns its argument unchanged — orchestration
272
+ * counterpart to `definePattern()`. The body is trivial on purpose; the
273
+ * point is to give TypeScript a hook so consumer fixtures get full
274
+ * compile-time checking against `OrchestrationPatternDefinition`.
275
+ */
276
+ export function defineOrchestrationPattern(
277
+ def: OrchestrationPatternDefinition,
278
+ ): OrchestrationPatternDefinition {
279
+ return def;
280
+ }