Skip to content

Commit 6077eba

Browse files
dmealingclaude
andcommitted
feat(codegen-ts): an inline field.enum gets a runtime constant, not just a type
The generated entity file tells its reader to "use these instead of magic strings", and for the one field kind where a typo is most likely it did not give them anything to use. A type alias is erased at runtime, so code needing a MEMBER had nothing to import — while the same member list appeared three or four more times in that same file (the Drizzle `enum:` option, the type union, each Zod schema). A package-level SHARED enum has always had a runtime constant; an inline one had none anywhere. An inline enum now emits `export const <Owner><Field>Enum = z.enum([...])` beside its alias, under the shared naming convention, so which kind of enum it is stops mattering at the call site. An entity file also re-exports a MATERIALIZED shared enum's constant beside its type. It deliberately does NOT for a `@provided` enum. That declaration lives in a module the ADOPTER owns and configured, and nothing here knows or can require that it exports a Zod constant — naming one emits an import of a symbol that may not exist and breaks their build. Only the type is contracted for a provided enum. The FR-019 conformance test caught the first attempt, which is the second time this release that the provided/materialized distinction has been the thing a change got wrong. Found on the public reference app running the 1.0 candidate, where the same status symbols appear as literals in five hand-written files. Gated on the declared case, the inherited-through-extends case, and one-const-per-alias. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3v6M6uqECZ2Sb5eUv6YY
1 parent f459625 commit 6077eba

10 files changed

Lines changed: 100 additions & 14 deletions

File tree

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,30 @@ something you identify (the closest published `src/reference/<name>.ts`). **A to
3434
which side of each conflict is a deliberate customization and which is stale, and that is
3535
judgement, not a three-way diff.
3636

37+
### Added — an inline `field.enum` gets a runtime constant, not just a type
38+
39+
A type alias is erased at runtime, so hand-written code needing a MEMBER had nothing to
40+
import and wrote the symbol as a literal — while the same member list appeared three or four
41+
more times inside the one generated file (the Drizzle `enum:` option, the type union, each
42+
Zod schema). A package-level **shared** enum has always had a runtime constant (`<E>Enum` in
43+
the shared enums module); an **inline** one had none anywhere, which made the generated
44+
file's own advice — *"use these instead of magic strings"* — something an adopter could not
45+
follow for the one field kind where a typo is most likely.
46+
47+
An inline `field.enum` now emits `export const <Owner><Field>Enum = z.enum([...])` beside its
48+
type alias, under the same name shape as the shared one, so which kind of enum it is stops
49+
mattering at the call site. `.options` is the member array and `.enum.<Member>` an individual
50+
symbol, in both Zod 3 and Zod 4.
51+
52+
An entity file also re-exports a **materialized** shared enum's constant beside its type, for
53+
the same reason. It deliberately does **not** for a `@provided` enum: that declaration lives
54+
in a module the adopter owns, and nothing here can require it to export a Zod constant —
55+
naming one would emit an import of a symbol that may not exist. Only the type is contracted
56+
for a provided enum, and the FR-019 conformance test caught the attempt.
57+
58+
Found on the public reference app running the 1.0 candidate, where the same status symbols
59+
appeared as literals across five hand-written files.
60+
3761
### Fixed — a `view: "dropdown"` descriptor carried no options
3862

3963
`0.25.0` moved a `field.enum`'s FORM view from `text` to `dropdown`. The generated

examples/advanced-modeling/.metaobjects/.gen-state/.hashes.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"src/generated/Program.form.tsx": "e3c03e5497b7b8d65758a71c0d43e4a0a73c3edcfa3eb25ebd69af3a1efe3c84",
1212
"src/generated/Program.queries.ts": "b9d6f55d56d933b6b081cacb7e144f08828b1c1b92c6eaa7126b0e02f9f65d34",
1313
"src/generated/Program.routes.ts": "b4b01ad507bac678d17c634ae5b89cb5f4ff7bc8596960278cb69cdd621b733a",
14-
"src/generated/Program.ts": "fc8ac56506859c2742e56681debb04e73effc17cfd89267e81101b8e6a815a6f",
14+
"src/generated/Program.ts": "a38a07dffdfeff955f1ba3e80b2a0328764d0c1f8757346d500097b91c4d636b",
1515
"src/generated/ProgramDescriptionOutput.output.ts": "f13b7b2108bf8507b6aeb1e819edd06eb2d689ed259dc04784d19d62bee9dfd2",
1616
"src/generated/ProgramDescriptionOutput.render.ts": "f9e939a98693e7da14b978e78d2a4291702e9e5563d73ba22ea1677f9707899b",
1717
"src/generated/ProgramDescriptionPayload.form.tsx": "a9dff101453450117aff3029b2c5259e7d0866667aaab722ba890beaaa20f95d",
@@ -22,7 +22,7 @@
2222
"src/generated/Purchase.form.tsx": "7ddb107c14a991df1b414830ee19ccbdc7f7bc4b29e648628b5bc13c34326921",
2323
"src/generated/Purchase.queries.ts": "53ad59b680f8530dba3f2309c30fda79f6157c09fcb5774b4db34db96a5364d8",
2424
"src/generated/Purchase.routes.ts": "2f3551c366c3e7d60010555c73e07d818718398d8f56244edf2066b5c99702fa",
25-
"src/generated/Purchase.ts": "112861bec89e4b1015f31e8c36f02c965a29ab20d1ab688220c1e4b792aec6f8",
25+
"src/generated/Purchase.ts": "0ba16258692e13d1bfd9512bbe38304d466cad4ae8fc0f13cc38c9a876a9d023",
2626
"src/generated/SyllabusSection.ts": "43336e7d774bf0405a17b7fdba556f98dba922b09b358d59e3cc6d3900fe2080",
2727
"src/generated/index.ts": "0f7e35f89de3edcb69227aec7341fdf400f8627799fe562d52d4f8cb8ce8451f",
2828
"src/generated/prompts.ts": "16f0650d11b95b23bec0a8547f26bdac399fa73a330868f18e43c845b0cdf8ab"

examples/advanced-modeling/src/generated/Program.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export type Program = InferSelectModel<typeof programs>;
5151
export type ProgramInsert = InferInsertModel<typeof programs>;
5252
export type ProgramUpdate = Partial<ProgramInsert>;
5353
export type ProgramStatus = "draft" | "published" | "archived";
54+
export const ProgramStatusEnum = z.enum(["draft", "published", "archived"]);
5455
export const ProgramInsertSchema = z.object({
5556
title: z.string().min(1).max(200),
5657
status: z.enum(["draft", "published", "archived"]).optional(),

examples/advanced-modeling/src/generated/Purchase.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export type Purchase = InferSelectModel<typeof purchases>;
4141
export type PurchaseInsert = InferInsertModel<typeof purchases>;
4242
export type PurchaseUpdate = Partial<PurchaseInsert>;
4343
export type PurchaseStatus = "pending" | "completed" | "refunded";
44+
export const PurchaseStatusEnum = z.enum(["pending", "completed", "refunded"]);
4445
export const PurchaseInsertSchema = z.object({
4546
programId: z.string(),
4647
amountCents: z.number().int().optional(),

examples/showcase/.metaobjects/.gen-state/.hashes.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"__init__.py": "daa04cec586768866d0996c779f62073e6773fe63750ff668327991fac2fd0f5",
1111
"generated/ts/Subscriber.queries.ts": "843e21d8ed84ecead64870b980dfa3156dd2f360ade1f1d2693a5f04bde0fb3c",
1212
"generated/ts/Subscriber.routes.ts": "05d57884708ed78bdad9d69744b1685bc7a310a988aec2edd2830b53b68b3315",
13-
"generated/ts/Subscriber.ts": "117690d3c55cf77c39e9d71881d20f0eb855c696799d2074d61475d44cd82ca2",
13+
"generated/ts/Subscriber.ts": "c3c1729c5c83cb8773cae76178343932d04080aec87c3a5d27916d4b412c2d33",
1414
"generated/ts/SubscriberBlurbPayload.ts": "613a80a6c0ce7e967f84f7af3c587a3c6f04434a67cea838344217ab6d132fb9",
1515
"generated/ts/index.ts": "b9f04c67786b5eec1aa493958d9843b86a58406228fcfc85634559fc4b2cbfa6",
1616
"generated/ts/prompts.ts": "9cd69070c5b9c1f4b1200422858ef34488c2e8aa8ea5339714629a8de72c81cb",

examples/showcase/generated/ts/Subscriber.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export type Subscriber = InferSelectModel<typeof subscribers>;
2727
export type SubscriberInsert = InferInsertModel<typeof subscribers>;
2828
export type SubscriberUpdate = Partial<SubscriberInsert>;
2929
export type SubscriberStatus = "active" | "paused" | "cancelled";
30+
export const SubscriberStatusEnum = z.enum(["active", "paused", "cancelled"]);
3031
export const SubscriberInsertSchema = z.object({
3132
email: z.string().min(1).max(320),
3233
name: z.string().optional(),

0 commit comments

Comments
 (0)