@pattern-stack/codegen 0.6.7 → 0.6.8
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
CHANGED
|
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.6.8] — 2026-04-28
|
|
8
|
+
|
|
9
|
+
Hotfix for enum codegen in the `clean-lite-ps` template pipeline. Surfaced during integration-patterns Wave 0b: enum-typed YAML fields emitted a Drizzle `text()` column instead of a `pgEnum`, so `InferSelectModel` resolved to `string` instead of the literal-union type and forced hand-casts in consumer code (e.g. `as DecryptedIntegrationRow['status']`).
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **`fix(codegen)` — clean-lite-ps enum emission.** `templates/entity/new/clean-lite-ps/prompt-extension.js` now produces `pgEnum` declarations + column references for any field with `choices` (or `type: enum`), matching the backend pipeline at `templates/entity/new/backend/database/schema.ejs.t:66-104`. Generated entity files now contain `export const xEnum = pgEnum('x', [...])` ahead of the `pgTable(...)` block and reference `xEnum('x').notNull()` inside the column map. `pgEnum` is added to the `drizzle-orm/pg-core` import list automatically when any enum field is present. The emitted `InferSelectModel` type now narrows to the literal union — consumers can drop `as Row['status']` casts. New unit coverage: `src/__tests__/clean-lite-ps/entity-enum-template.test.ts`.
|
|
14
|
+
|
|
15
|
+
### Migration note
|
|
16
|
+
|
|
17
|
+
Not a breaking change for the emitted code shape (the field's TypeScript type narrows — a strict superset of what consumer code can do). Existing Postgres databases generated against 0.6.7 or earlier will need a one-time `CREATE TYPE … AS ENUM (...)` + `ALTER TABLE … ALTER COLUMN x TYPE x_enum USING x::x_enum;` migration, since the column was previously `text`. Greenfield projects, or anyone regenerating before the first migration runs, are unaffected.
|
|
18
|
+
|
|
7
19
|
## [0.6.7] — 2026-04-28
|
|
8
20
|
|
|
9
21
|
Hotfix bundle for `cdp subsystem install auth-integrations`. 0.6.5 / 0.6.6 shipped the auth-integrations starter and install template, but every downstream consumer ran into four blockers on a fresh install. None of them surfaced from the source-checkout smoke (the install code resolves examples/ via the package root, which exists in dev) — they only exposed themselves through `npm install + bunx cdp subsystem install auth-integrations` against the published tarball. Bundles a fifth fix that unifies the integrations folder layout. Surfaced by integration-patterns Wave 0b.
|
package/package.json
CHANGED
|
@@ -18,6 +18,12 @@ import { type InferSelectModel } from 'drizzle-orm';
|
|
|
18
18
|
import { <%= rel.relatedTable %> } from '<%= rel.importPath %>';
|
|
19
19
|
<%_ } _%>
|
|
20
20
|
<%_ }) _%>
|
|
21
|
+
<%_ if (typeof clpEnumFields !== 'undefined' && clpEnumFields.length > 0) { _%>
|
|
22
|
+
|
|
23
|
+
<%_ clpEnumFields.forEach(ef => { _%>
|
|
24
|
+
export const <%= ef.enumName %> = pgEnum('<%= ef.dbName %>', [<%- ef.choices.map(c => `'${c}'`).join(', ') %>]);
|
|
25
|
+
<%_ }) _%>
|
|
26
|
+
<%_ } _%>
|
|
21
27
|
|
|
22
28
|
export const <%= entityNamePlural %> = pgTable(
|
|
23
29
|
'<%= entityNamePlural %>',
|
|
@@ -201,7 +201,7 @@ const EXTERNAL_ID_TRACKING_FIELDS = new Set([
|
|
|
201
201
|
/**
|
|
202
202
|
* Build a Drizzle column chain for a field
|
|
203
203
|
*/
|
|
204
|
-
function buildDrizzleChain(fieldName, field, drizzleType) {
|
|
204
|
+
function buildDrizzleChain(fieldName, field, drizzleType, enumName) {
|
|
205
205
|
const nullable = field.nullable ?? false;
|
|
206
206
|
const required = field.required ?? false;
|
|
207
207
|
const hasDefault = field.default !== undefined && field.default !== null;
|
|
@@ -211,7 +211,11 @@ function buildDrizzleChain(fieldName, field, drizzleType) {
|
|
|
211
211
|
// schemas using z.coerce.date() align with the entity type.
|
|
212
212
|
// `timestamp` already defaults to Date — no mode override needed.
|
|
213
213
|
let chain;
|
|
214
|
-
if (drizzleType === '
|
|
214
|
+
if (drizzleType === 'enum' && enumName) {
|
|
215
|
+
// Reference the pgEnum declaration emitted at the top of the entity file.
|
|
216
|
+
// The column name argument keeps the snake_case YAML field name.
|
|
217
|
+
chain = `${enumName}('${fieldName}')`;
|
|
218
|
+
} else if (drizzleType === 'date') {
|
|
215
219
|
chain = `${drizzleType}('${fieldName}', { mode: 'date' })`;
|
|
216
220
|
} else {
|
|
217
221
|
chain = `${drizzleType}('${fieldName}')`;
|
|
@@ -247,7 +251,15 @@ function processFields(fields) {
|
|
|
247
251
|
const choices = field.choices;
|
|
248
252
|
const hasChoices = Array.isArray(choices) && choices.length > 0;
|
|
249
253
|
|
|
250
|
-
|
|
254
|
+
// Enum-typed fields (or any field with a `choices` list) emit a
|
|
255
|
+
// Postgres-native pgEnum declaration + column reference, so the
|
|
256
|
+
// generated `InferSelectModel` type narrows to the literal union
|
|
257
|
+
// instead of falling back to `string`. Matches the backend pipeline
|
|
258
|
+
// (templates/entity/new/backend/database/schema.ejs.t:66-104).
|
|
259
|
+
const drizzleType = hasChoices
|
|
260
|
+
? 'enum'
|
|
261
|
+
: (DRIZZLE_TYPE_MAP[type] || 'text');
|
|
262
|
+
const enumName = hasChoices ? camelCase(fieldName) + 'Enum' : null;
|
|
251
263
|
const tsType = hasChoices
|
|
252
264
|
? choices.map((c) => `'${c}'`).join(' | ')
|
|
253
265
|
: (TS_TYPE_MAP[type] || 'unknown');
|
|
@@ -255,7 +267,7 @@ function processFields(fields) {
|
|
|
255
267
|
? `z.enum([${choices.map((c) => `'${c}'`).join(', ')}])`
|
|
256
268
|
: (ZOD_TYPE_MAP[type] || 'z.unknown()');
|
|
257
269
|
|
|
258
|
-
const drizzleChain = buildDrizzleChain(fieldName, field, drizzleType);
|
|
270
|
+
const drizzleChain = buildDrizzleChain(fieldName, field, drizzleType, enumName);
|
|
259
271
|
|
|
260
272
|
processed.push({
|
|
261
273
|
name: fieldName,
|
|
@@ -271,6 +283,7 @@ function processFields(fields) {
|
|
|
271
283
|
drizzleChain,
|
|
272
284
|
choices,
|
|
273
285
|
hasChoices,
|
|
286
|
+
enumName,
|
|
274
287
|
});
|
|
275
288
|
}
|
|
276
289
|
|
|
@@ -355,6 +368,12 @@ function collectDrizzleImports(processedFields, belongsTo, hasTimestamps, hasSof
|
|
|
355
368
|
const imports = new Set(['pgTable', 'uuid']);
|
|
356
369
|
|
|
357
370
|
for (const field of processedFields) {
|
|
371
|
+
if (field.drizzleType === 'enum') {
|
|
372
|
+
// Enum columns reference a `pgEnum` declaration emitted at the top
|
|
373
|
+
// of the entity file; the helper itself comes from drizzle-orm/pg-core.
|
|
374
|
+
imports.add('pgEnum');
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
358
377
|
const importName = DRIZZLE_IMPORT_MAP[field.drizzleType];
|
|
359
378
|
if (importName) imports.add(importName);
|
|
360
379
|
}
|
|
@@ -783,6 +802,18 @@ export function buildCleanLitePsLocals(definition, baseLocals) {
|
|
|
783
802
|
const fkFieldNames = new Set(belongsTo.map((r) => r.field));
|
|
784
803
|
const nonFkFields = processedFields.filter((f) => !fkFieldNames.has(f.name));
|
|
785
804
|
|
|
805
|
+
// Enum field declarations — surface a separate collection so the entity
|
|
806
|
+
// template can emit `export const xEnum = pgEnum('x', [...])` ahead of
|
|
807
|
+
// the `pgTable(...)` block. Both FK-filtered and unfiltered processing
|
|
808
|
+
// include the same enum fields; they're never FKs.
|
|
809
|
+
const clpEnumFields = processedFields
|
|
810
|
+
.filter((f) => f.hasChoices && f.enumName)
|
|
811
|
+
.map((f) => ({
|
|
812
|
+
enumName: f.enumName,
|
|
813
|
+
dbName: f.name,
|
|
814
|
+
choices: f.choices,
|
|
815
|
+
}));
|
|
816
|
+
|
|
786
817
|
// Drizzle imports needed
|
|
787
818
|
const drizzleEntityImports = collectDrizzleImports(processedFields, belongsTo, hasTimestamps, hasSoftDelete, hasExternalIdTracking);
|
|
788
819
|
// Whether relations() import is needed
|
|
@@ -980,6 +1011,7 @@ export function buildCleanLitePsLocals(definition, baseLocals) {
|
|
|
980
1011
|
// Drizzle
|
|
981
1012
|
clpDrizzleImports: drizzleEntityImports,
|
|
982
1013
|
clpHasRelationsBlock: hasRelationsBlock,
|
|
1014
|
+
clpEnumFields,
|
|
983
1015
|
|
|
984
1016
|
// Declarative queries
|
|
985
1017
|
processedQueries,
|