From eebbf3b202b4e4a144839014558777c01c2ab219 Mon Sep 17 00:00:00 2001 From: cultron Date: Tue, 27 Oct 2026 18:29:57 -0500 Subject: [PATCH] fix(typescript): boolean columns default to false unless the default is true entity-col-boolean.eta wrote default: Boolean(field.default) || false. A boolean with no default is computed as the string "false", and Boolean("false") is true, so every boolean without a default, and every one with "default": "false", got DEFAULT true in the database. The template now writes true only for a default of true or "true". Existing services: the next migration generated against these entities will change those columns to DEFAULT false. It does not touch rows already stored; rows created without the field set hold true. --- .../templates/entities/entity-col-boolean.eta | 2 +- test/lib/generators/typescript.test.ts | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/lib/templates/entities/entity-col-boolean.eta b/src/lib/templates/entities/entity-col-boolean.eta index b0a7b3a..1b6360b 100644 --- a/src/lib/templates/entities/entity-col-boolean.eta +++ b/src/lib/templates/entities/entity-col-boolean.eta @@ -3,7 +3,7 @@ @IsOptional({ groups: [CREATE, UPDATE] }) @IsBoolean({ always: true }) -@Column({ type: 'boolean', nullable: <%= Boolean(field.nullable) %>, default: <%= Boolean(field.default) || false %> }) +@Column({ type: 'boolean', nullable: <%= Boolean(field.nullable) %>, default: <%= field.default === true || String(field.default).toLowerCase() === 'true' %> }) <% if (field.index ) {%> @Index() <% } %> diff --git a/test/lib/generators/typescript.test.ts b/test/lib/generators/typescript.test.ts index 2003ecb..2ef276b 100644 --- a/test/lib/generators/typescript.test.ts +++ b/test/lib/generators/typescript.test.ts @@ -227,6 +227,39 @@ describe("TypeScriptGenerator", () => { expect(entityContent).toContain('@Column({ "type": "uuid", nullable: true })'); }); + test("boolean defaults are false unless the default is true", async () => { + const entity: Entity = { + name: "Flag", + primaryKeyType: "serial", + fields: [ + { name: "noDefault", type: "boolean" }, + { name: "falseString", type: "boolean", default: "false" }, + { name: "falseBool", type: "boolean", default: false }, + { name: "trueString", type: "boolean", default: "true" }, + { name: "trueBool", type: "boolean", default: true }, + ] as unknown as Field[], + }; + + const files = await generator.generateEntity({ + entity, + relationships: [], + allEntities: [entity], + apiType: "rest", + }); + + const content = findFileContent(files, "Flag.entity")!; + const defaultOf = (name: string) => + content + .split("@Column(") + .find((c) => c.includes(`${name}!:`)) + ?.match(/default: (\w+)/)?.[1]; + expect(defaultOf("noDefault")).toBe("false"); + expect(defaultOf("falseString")).toBe("false"); + expect(defaultOf("falseBool")).toBe("false"); + expect(defaultOf("trueString")).toBe("true"); + expect(defaultOf("trueBool")).toBe("true"); + }); + test("timestamptz, timestamp, and datetime generate instant-in-time columns honoring nullable", async () => { const entity: Entity = { name: "Event",