Skip to content

[finding] packages/cli generate.ts: both migration generators ignore multiple: true, so a multi-valued field gets a scalar column while os generate types gives it an array type #14829

Description

@os-trump

Found while implementing #14657. Filed unassigned for triage. No severity asserted. #14657 is not addressed here and remains open until its own PR lands.

The shape

multiple appears exactly four times in packages/cli/src/commands/generate.ts, and all four are on the TypeScript side:

562  function fieldTypeToTs(fieldType: string, multiple?: boolean): string {
564    return multiple ? `${base}[]` : base;
607      const tsType = fieldTypeToTs(fType, !!fieldDef.multiple);   // os generate types
831      const tsType = fieldTypeToTs(fType, !!fieldDef.multiple);   // os generate client

generateMigrationSql and generateMigrationTs never read it. fieldTypeToSql(fieldType) does not even take the parameter.

So for Field.lookup({ reference: 'account', multiple: true }) the CLI emits, from one config in one run:

  • os generate typesaccount?: string[]
  • os generate migration --format sqlVARCHAR(36)
  • os generate migration (typescript, the default) — table.uuid('account')

The runtime disagrees with the last two. driver-sql's isJsonField is JSON_COLUMN_TYPES.has(type) || !!field.multiple — the multiple flag alone puts the column in the JSON class, and sql-driver.ts handles it "at the top of createColumn", before the per-type switch. So a multiple: true field of ANY multi-capable type (MULTI_CAPABLE_TYPES = select, radio, lookup, user, file, image) is a JSON column in the platform and a scalar column in the generated migration.

Why it is a defect rather than a gap

It is silent in both directions: the scaffold looks right, the generated TypeScript is right, and only the column is wrong. The write path then serializes an array into a scalar column — the same class of failure #field-zoo recorded when array-valued fields reached the SQLite binder unserialized ("SQLite3 can only bind numbers, strings, bigints, buffers, and null"), except here it is the generated DDL rather than the driver that is out of step.

Why it was not folded into #14657

#14657 is about members with no vocabulary entry. This is orthogonal: it affects members that HAVE an entry, through a flag no migration generator reads, and the fix is a parameter through fieldTypeToSql plus a pre-switch branch in generateMigrationTs — a change to the generators' shape rather than to any vocabulary. Filing it separately keeps that decision out of a card whose whole point was that hand-carried lists drift.

Notes for triage

Dedup

search_issues for this shape returned total_count: 0; a control query in the same session returned #14657 and #13871, so the empty result is a reading rather than a broken search.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions