From 7b4493ca6056929f7303e6cf0233378fcb23f934 Mon Sep 17 00:00:00 2001 From: Doug Mealing Date: Sat, 4 Jul 2026 12:10:35 -0400 Subject: [PATCH] fix(codegen-ts): emit type-only Drizzle symbols with import type (verbatimModuleSyntax) (#165) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The default entityFile() output imported Drizzle's type-only symbols as VALUE imports: InferSelectModel / InferInsertModel (drizzle-orm) and AnyPgColumn / AnySQLiteColumn (the *-core package, used only as a .references() return-type annotation). Under `verbatimModuleSyntax: true` — a common default in modern Vite/TS app templates — tsc rejects each with TS1484 ("… is a type and must be imported using a type-only import"), so a generated DAO fails `tsc -b` with hundreds of errors even though it runs fine under a bundler. Mark the three symbols type-only via ts-poet's `t:` prefix (the idiom already used for FastifyInstance/Hono/TPH imports), so they emit as `import type` (or an inline `type` modifier when mixed with value imports from the same module). This fixes both the built-in generator and the ADR-0034 scaffold-and-own reference template, which delegates to the same renderDrizzleSchema / renderInferredTypes primitives. Adds a real-tsc compile guard with verbatimModuleSyntax ON over an entity with a self-referential FK (so AnyPgColumn is exercised); a negative control confirms it catches the TS1484 regression for all three symbols. Golden snapshots updated (the only change is value→type imports). TS-only; codegen output, no metamodel or cross-port impact. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Ew1XfYSbEAezxjs9opynAe --- .../src/templates/drizzle-schema.ts | 4 +- .../src/templates/inferred-types.ts | 6 +- .../test/golden/__snapshots__/package/Tag.ts | 8 +- .../package/shop/commerce/Order.ts | 12 +- .../package/shop/commerce/Product.ts | 8 +- .../package/shop/users/Customer.ts | 2 +- .../golden/__snapshots__/postgres/Exercise.ts | 8 +- .../golden/__snapshots__/postgres/Program.ts | 2 +- .../golden/__snapshots__/postgres/Purchase.ts | 8 +- .../__snapshots__/postgres/Subscriber.ts | 2 +- .../test/golden/__snapshots__/postgres/Tag.ts | 2 +- .../golden/__snapshots__/postgres/Video.ts | 8 +- .../golden/__snapshots__/postgres/Week.ts | 8 +- .../golden/__snapshots__/postgres/Workout.ts | 8 +- .../__snapshots__/postgres/WorkoutEvent.ts | 8 +- .../golden/__snapshots__/sqlite/Exercise.ts | 8 +- .../golden/__snapshots__/sqlite/Program.ts | 2 +- .../golden/__snapshots__/sqlite/Purchase.ts | 6 +- .../golden/__snapshots__/sqlite/Subscriber.ts | 2 +- .../test/golden/__snapshots__/sqlite/Tag.ts | 2 +- .../test/golden/__snapshots__/sqlite/Video.ts | 8 +- .../test/golden/__snapshots__/sqlite/Week.ts | 8 +- .../golden/__snapshots__/sqlite/Workout.ts | 8 +- .../__snapshots__/sqlite/WorkoutEvent.ts | 6 +- .../verbatim-module-syntax-compile.test.ts | 124 ++++++++++++++++++ 25 files changed, 226 insertions(+), 42 deletions(-) create mode 100644 server/typescript/packages/codegen-ts/test/verbatim-module-syntax-compile.test.ts diff --git a/server/typescript/packages/codegen-ts/src/templates/drizzle-schema.ts b/server/typescript/packages/codegen-ts/src/templates/drizzle-schema.ts index 77906a1d9..2be9ca4cf 100644 --- a/server/typescript/packages/codegen-ts/src/templates/drizzle-schema.ts +++ b/server/typescript/packages/codegen-ts/src/templates/drizzle-schema.ts @@ -344,7 +344,9 @@ function renderColumn( // initializer") under `strict`. The annotation is a harmless explicit supertype // for acyclic FKs, so emitting it unconditionally is safe. const anyColType = ctx.dialect === "sqlite" ? "AnySQLiteColumn" : "AnyPgColumn"; - const anyColSym = imp(`${anyColType}@${spec.importModule}`); + // Used only as a return-type annotation → type-only import (t:) so it emits + // `import type` and doesn't fail tsc under `verbatimModuleSyntax` (TS1484). (#165) + const anyColSym = imp(`t:${anyColType}@${spec.importModule}`); if (fkInfo.targetEntityName === currentEntityName) { // Self-referential FK (e.g. createdBy → this same table): reference the local // table const directly — NOT a self-import. diff --git a/server/typescript/packages/codegen-ts/src/templates/inferred-types.ts b/server/typescript/packages/codegen-ts/src/templates/inferred-types.ts index a00c0895b..ae86d08fd 100644 --- a/server/typescript/packages/codegen-ts/src/templates/inferred-types.ts +++ b/server/typescript/packages/codegen-ts/src/templates/inferred-types.ts @@ -57,8 +57,10 @@ export function renderInferredTypes(entity: MetaObject, tphBase = false, ctx?: R // ctx is optional for bare unit-test calls — those fall back to the default // always-pluralize spelling. const varName = ctx ? ctx.collectionName(entity.name) : variableNameFromEntity(entity.name); - const selectSym = imp("InferSelectModel@drizzle-orm"); - const insertSym = imp("InferInsertModel@drizzle-orm"); + // Type-only symbols (t: prefix) so ts-poet emits `import type` — otherwise a + // value import of these types fails tsc under `verbatimModuleSyntax` (TS1484). (#165) + const selectSym = imp("t:InferSelectModel@drizzle-orm"); + const insertSym = imp("t:InferInsertModel@drizzle-orm"); const docs = renderDocsFor(entity); const docsPrefix = docs ? `${docs}\n` : ""; const rowName = tphBase ? `${entity.name}Row` : entity.name; diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/package/Tag.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/package/Tag.ts index 2f0d7a947..43f4195d2 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/package/Tag.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/package/Tag.ts @@ -1,9 +1,13 @@ // @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. // Source metadata: Tag (Tag) // Customize via Tag.extra.ts in this directory. -import { InferInsertModel, InferSelectModel, relations } from "drizzle-orm"; import { - AnySQLiteColumn, + type InferInsertModel, + type InferSelectModel, + relations, +} from "drizzle-orm"; +import { + type AnySQLiteColumn, integer, sqliteTable, text, diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/package/shop/commerce/Order.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/package/shop/commerce/Order.ts index 2188a9768..e0064a5b9 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/package/shop/commerce/Order.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/package/shop/commerce/Order.ts @@ -1,8 +1,16 @@ // @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. // Source metadata: Order (shop::commerce::Order) // Customize via Order.extra.ts in this directory. -import { InferInsertModel, InferSelectModel, relations } from "drizzle-orm"; -import { AnySQLiteColumn, integer, sqliteTable } from "drizzle-orm/sqlite-core"; +import { + type InferInsertModel, + type InferSelectModel, + relations, +} from "drizzle-orm"; +import { + type AnySQLiteColumn, + integer, + sqliteTable, +} from "drizzle-orm/sqlite-core"; import { z } from "zod"; import { customers } from "../users/Customer"; import { products } from "./Product"; diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/package/shop/commerce/Product.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/package/shop/commerce/Product.ts index 41968c30f..40b4f4551 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/package/shop/commerce/Product.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/package/shop/commerce/Product.ts @@ -1,9 +1,13 @@ // @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. // Source metadata: Product (shop::commerce::Product) // Customize via Product.extra.ts in this directory. -import { InferInsertModel, InferSelectModel, relations } from "drizzle-orm"; import { - AnySQLiteColumn, + type InferInsertModel, + type InferSelectModel, + relations, +} from "drizzle-orm"; +import { + type AnySQLiteColumn, integer, sqliteTable, text, diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/package/shop/users/Customer.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/package/shop/users/Customer.ts index e720f12b4..de1ad34be 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/package/shop/users/Customer.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/package/shop/users/Customer.ts @@ -1,7 +1,7 @@ // @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. // Source metadata: Customer (shop::users::Customer) // Customize via Customer.extra.ts in this directory. -import { InferInsertModel, InferSelectModel } from "drizzle-orm"; +import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; import { z } from "zod"; diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Exercise.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Exercise.ts index d8d0f24e7..e37c0d50a 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Exercise.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Exercise.ts @@ -1,9 +1,13 @@ // @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. // Source metadata: Exercise (Exercise) // Customize via Exercise.extra.ts in this directory. -import { InferInsertModel, InferSelectModel, relations } from "drizzle-orm"; import { - AnyPgColumn, + type InferInsertModel, + type InferSelectModel, + relations, +} from "drizzle-orm"; +import { + type AnyPgColumn, bigint, bigserial, integer, diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Program.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Program.ts index f5e2252cd..a3207f123 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Program.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Program.ts @@ -1,7 +1,7 @@ // @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. // Source metadata: Program (Program) // Customize via Program.extra.ts in this directory. -import { InferInsertModel, InferSelectModel } from "drizzle-orm"; +import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; import { bigserial, boolean, diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Purchase.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Purchase.ts index 8182d2f3c..2792c5a2e 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Purchase.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Purchase.ts @@ -1,9 +1,13 @@ // @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. // Source metadata: Purchase (Purchase) // Customize via Purchase.extra.ts in this directory. -import { InferInsertModel, InferSelectModel, relations } from "drizzle-orm"; import { - AnyPgColumn, + type InferInsertModel, + type InferSelectModel, + relations, +} from "drizzle-orm"; +import { + type AnyPgColumn, bigint, bigserial, integer, diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Subscriber.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Subscriber.ts index cf49cc901..dae4269ce 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Subscriber.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Subscriber.ts @@ -1,7 +1,7 @@ // @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. // Source metadata: Subscriber (Subscriber) // Customize via Subscriber.extra.ts in this directory. -import { InferInsertModel, InferSelectModel } from "drizzle-orm"; +import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; import { bigserial, boolean, diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Tag.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Tag.ts index ec1303e3f..b37fb44c6 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Tag.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Tag.ts @@ -1,7 +1,7 @@ // @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. // Source metadata: Tag (Tag) // Customize via Tag.extra.ts in this directory. -import { InferInsertModel, InferSelectModel } from "drizzle-orm"; +import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; import { bigserial, pgTable, varchar } from "drizzle-orm/pg-core"; import { z } from "zod"; diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Video.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Video.ts index 1bf988f54..d8edb907b 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Video.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Video.ts @@ -1,9 +1,13 @@ // @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. // Source metadata: Video (Video) // Customize via Video.extra.ts in this directory. -import { InferInsertModel, InferSelectModel, relations } from "drizzle-orm"; import { - AnyPgColumn, + type InferInsertModel, + type InferSelectModel, + relations, +} from "drizzle-orm"; +import { + type AnyPgColumn, bigint, bigserial, integer, diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Week.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Week.ts index 8a598470a..b55ec2aed 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Week.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Week.ts @@ -1,9 +1,13 @@ // @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. // Source metadata: Week (Week) // Customize via Week.extra.ts in this directory. -import { InferInsertModel, InferSelectModel, relations } from "drizzle-orm"; import { - AnyPgColumn, + type InferInsertModel, + type InferSelectModel, + relations, +} from "drizzle-orm"; +import { + type AnyPgColumn, bigint, bigserial, integer, diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Workout.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Workout.ts index 05860f184..4ed17b841 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Workout.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/Workout.ts @@ -1,9 +1,13 @@ // @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. // Source metadata: Workout (Workout) // Customize via Workout.extra.ts in this directory. -import { InferInsertModel, InferSelectModel, relations } from "drizzle-orm"; import { - AnyPgColumn, + type InferInsertModel, + type InferSelectModel, + relations, +} from "drizzle-orm"; +import { + type AnyPgColumn, bigint, bigserial, integer, diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/WorkoutEvent.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/WorkoutEvent.ts index 76042666e..35eddf2f7 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/WorkoutEvent.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/postgres/WorkoutEvent.ts @@ -1,9 +1,13 @@ // @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. // Source metadata: WorkoutEvent (WorkoutEvent) // Customize via WorkoutEvent.extra.ts in this directory. -import { InferInsertModel, InferSelectModel, relations } from "drizzle-orm"; import { - AnyPgColumn, + type InferInsertModel, + type InferSelectModel, + relations, +} from "drizzle-orm"; +import { + type AnyPgColumn, bigint, bigserial, integer, diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Exercise.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Exercise.ts index 2c390b86e..29d069588 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Exercise.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Exercise.ts @@ -1,9 +1,13 @@ // @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. // Source metadata: Exercise (Exercise) // Customize via Exercise.extra.ts in this directory. -import { InferInsertModel, InferSelectModel, relations } from "drizzle-orm"; import { - AnySQLiteColumn, + type InferInsertModel, + type InferSelectModel, + relations, +} from "drizzle-orm"; +import { + type AnySQLiteColumn, integer, sqliteTable, text, diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Program.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Program.ts index 8157edacc..e62594f62 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Program.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Program.ts @@ -1,7 +1,7 @@ // @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. // Source metadata: Program (Program) // Customize via Program.extra.ts in this directory. -import { InferInsertModel, InferSelectModel, sql } from "drizzle-orm"; +import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; import { z } from "zod"; diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Purchase.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Purchase.ts index 23ba70260..ac77ae341 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Purchase.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Purchase.ts @@ -2,13 +2,13 @@ // Source metadata: Purchase (Purchase) // Customize via Purchase.extra.ts in this directory. import { - InferInsertModel, - InferSelectModel, + type InferInsertModel, + type InferSelectModel, relations, sql, } from "drizzle-orm"; import { - AnySQLiteColumn, + type AnySQLiteColumn, integer, sqliteTable, text, diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Subscriber.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Subscriber.ts index 8c6404524..9e5397d3c 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Subscriber.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Subscriber.ts @@ -1,7 +1,7 @@ // @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. // Source metadata: Subscriber (Subscriber) // Customize via Subscriber.extra.ts in this directory. -import { InferInsertModel, InferSelectModel, sql } from "drizzle-orm"; +import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; import { z } from "zod"; diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Tag.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Tag.ts index ec1c556a3..934ae3f2b 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Tag.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Tag.ts @@ -1,7 +1,7 @@ // @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. // Source metadata: Tag (Tag) // Customize via Tag.extra.ts in this directory. -import { InferInsertModel, InferSelectModel } from "drizzle-orm"; +import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; import { z } from "zod"; diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Video.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Video.ts index 6892456d9..6941bd92a 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Video.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Video.ts @@ -1,9 +1,13 @@ // @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. // Source metadata: Video (Video) // Customize via Video.extra.ts in this directory. -import { InferInsertModel, InferSelectModel, relations } from "drizzle-orm"; import { - AnySQLiteColumn, + type InferInsertModel, + type InferSelectModel, + relations, +} from "drizzle-orm"; +import { + type AnySQLiteColumn, integer, sqliteTable, text, diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Week.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Week.ts index 6cbdb68c3..305914954 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Week.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Week.ts @@ -1,9 +1,13 @@ // @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. // Source metadata: Week (Week) // Customize via Week.extra.ts in this directory. -import { InferInsertModel, InferSelectModel, relations } from "drizzle-orm"; import { - AnySQLiteColumn, + type InferInsertModel, + type InferSelectModel, + relations, +} from "drizzle-orm"; +import { + type AnySQLiteColumn, integer, sqliteTable, text, diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Workout.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Workout.ts index 0bb6c3a44..563b960ed 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Workout.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/Workout.ts @@ -1,9 +1,13 @@ // @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. // Source metadata: Workout (Workout) // Customize via Workout.extra.ts in this directory. -import { InferInsertModel, InferSelectModel, relations } from "drizzle-orm"; import { - AnySQLiteColumn, + type InferInsertModel, + type InferSelectModel, + relations, +} from "drizzle-orm"; +import { + type AnySQLiteColumn, integer, sqliteTable, text, diff --git a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/WorkoutEvent.ts b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/WorkoutEvent.ts index 1bb58f994..3c556a3c3 100644 --- a/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/WorkoutEvent.ts +++ b/server/typescript/packages/codegen-ts/test/golden/__snapshots__/sqlite/WorkoutEvent.ts @@ -2,13 +2,13 @@ // Source metadata: WorkoutEvent (WorkoutEvent) // Customize via WorkoutEvent.extra.ts in this directory. import { - InferInsertModel, - InferSelectModel, + type InferInsertModel, + type InferSelectModel, relations, sql, } from "drizzle-orm"; import { - AnySQLiteColumn, + type AnySQLiteColumn, integer, sqliteTable, text, diff --git a/server/typescript/packages/codegen-ts/test/verbatim-module-syntax-compile.test.ts b/server/typescript/packages/codegen-ts/test/verbatim-module-syntax-compile.test.ts new file mode 100644 index 000000000..d5849f077 --- /dev/null +++ b/server/typescript/packages/codegen-ts/test/verbatim-module-syntax-compile.test.ts @@ -0,0 +1,124 @@ +// Regression guard (#165): the default entityFile() output must type-check under +// `verbatimModuleSyntax: true` (a common default in modern Vite/TS app templates). +// +// The generated Drizzle DAO imports type-only symbols — InferSelectModel / +// InferInsertModel (drizzle-orm) and AnyPgColumn / AnySQLiteColumn (the *-core +// package, used only as a .references() return-type annotation). Emitting them as +// VALUE imports fails tsc with TS1484 ("… is a type and must be imported using a +// type-only import when 'verbatimModuleSyntax' is enabled") — hundreds of errors +// per DAO even though the code runs fine under a bundler. The fix marks them +// type-only (ts-poet `t:` prefix) so they emit as `import type` / inline `type`. +// +// This compiles the REAL generated output with `verbatimModuleSyntax` on (the +// exact flag that surfaces the bug), so the guarantee holds by construction, not +// by string-match. The entity carries a self-referential FK so the AnyPgColumn +// annotation is actually emitted. + +import { describe, test, expect } from "bun:test"; +import { mkdtempSync, writeFileSync, rmSync } from "node:fs"; +import { join } from "node:path"; +import ts from "typescript"; +import { MetaDataLoader, InMemoryStringSource } from "@metaobjectsdev/metadata"; +import { entityFile } from "../src/generators/index.js"; +import { makeRenderContext } from "../src/render-context.js"; +import { buildPkMap } from "../src/pk-resolver.js"; +import { buildRelationMap } from "../src/relation-resolver.js"; +import type { GenContext } from "../src/generator.js"; +import type { Dialect } from "../src/metaobjects-config.js"; + +async function loadRoot() { + const json = JSON.stringify({ + "metadata.root": { + package: "test", + children: [ + { + "object.entity": { + name: "Node", + children: [ + { "source.rdb": { "@table": "nodes" } }, + { "field.int": { name: "id" } }, + // A self-referential FK → emits the `.references((): AnyPgColumn => …)` + // annotation, so the type-only *-core import is exercised. + { "field.int": { name: "parentId" } }, + { "field.string": { name: "label" } }, + { "identity.primary": { name: "id", "@fields": "id" } }, + { + "identity.reference": { + name: "fkParent", + "@fields": "parentId", + "@references": "Node", + }, + }, + ], + }, + }, + ], + }, + }); + const result = await new MetaDataLoader().load([new InMemoryStringSource(json)]); + if (result.errors.length > 0) { + throw new Error(`Loader errors:\n${result.errors.map((e) => e.message).join("\n")}`); + } + return result.root; +} + +// Real TS compiler gate, with verbatimModuleSyntax ON — the flag that turns a +// value import of a type into TS1484. +function compile(dir: string, files: string[]): readonly ts.Diagnostic[] { + const program = ts.createProgram( + files.map((f) => join(dir, f)), + { + strict: true, + noEmit: true, + verbatimModuleSyntax: true, + target: ts.ScriptTarget.ES2022, + module: ts.ModuleKind.ESNext, + moduleResolution: ts.ModuleResolutionKind.Bundler, + skipLibCheck: true, + }, + ); + return ts.getPreEmitDiagnostics(program); +} + +async function genAndCompile(dialect: Dialect): Promise { + const root = await loadRoot(); + const dir = mkdtempSync(join(import.meta.dir, "tmp-verbatim-")); + try { + const renderContext = makeRenderContext({ + dialect, + loadedRoot: root, + outDir: dir, + dbImport: "~/db", + pkMap: buildPkMap(root), + relationMap: buildRelationMap(root), + }); + const ctx: GenContext = { + entities: root.objects(), + loadedRoot: root, + matches: () => true, + projectRoot: dir, + config: { outDir: dir, extStyle: "none", dbImport: "~/db", dialect } as never, + renderContext, + warn: () => {}, + }; + const files = await entityFile({ allowlists: false }).generate(ctx); + for (const f of files) writeFileSync(join(dir, f.path), f.content); + return compile(dir, files.map((f) => f.path)); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +} + +describe("entityFile output type-checks under verbatimModuleSyntax (TS1484 regression guard, #165)", () => { + test("Postgres: zero diagnostics — no value import of a type-only symbol", async () => { + const diagnostics = await genAndCompile("postgres"); + const messages = diagnostics.map((d) => ts.flattenDiagnosticMessageText(d.messageText, "\n")); + expect(messages).toEqual([]); + }); + + test("SQLite: zero diagnostics — no value import of a type-only symbol", async () => { + const diagnostics = await genAndCompile("sqlite"); + const messages = diagnostics.map((d) => ts.flattenDiagnosticMessageText(d.messageText, "\n")); + expect(messages).toEqual([]); + }); +});