Skip to content

ImportWizardProps.dataSource is required, but every read of it in the component is defensive #4320

Description

@yinlianghui

Found while wiring @object-ui/plugin-grid's tests into tsc for #4040 (tranche 2, PR #4297). Recorded rather than changed: it is a public exported interface, and #4040's standing rule is to stop and file when a public type looks wrong rather than guess at it.

What the compiler said

ImportWizardProps.dataSource (packages/plugin-grid/src/ImportWizard.tsx:292) is declared dataSource: any — required. src/__tests__/importMissingRequiredHint.test.tsx omitted it, which is what the newly-compiled test project reported:

src/__tests__/importMissingRequiredHint.test.tsx(35,6): error TS2741: Property 'dataSource' is missing in
type '{ objectName: string; fields: …[]; open: true; onOpenChange: () => void; }'
but required in type 'ImportWizardProps'.

PR #4297 fixed the fixture (it passes a capability-free {}), which is the right fix for that test and is where the matter ends for #4040.

The observation

The fixture was omitting it and the wizard rendered fine — because every read of dataSource inside the component is guarded. There is no unguarded dereference anywhere in the file:

  • const ds = dataSource as Partial< DataSource > | undefined; — three times (:1432, :1632, :1883)
  • (dataSource as { listImportMappings?: … } | undefined)?.listImportMappings (:1673)
  • if (dataSource?.create) await dataSource.create(…) (:1861)
  • typeof (dataSource as Partial< DataSource > | undefined)?.listImportJobs === 'function' (:2188), and the same shape for importRecords (:2303) and cancelImportJob (:2353)

So the implementation's real contract is "optional, and every capability on it is optional too" — the wizard degrades from the server import path to the per-row create loop to nothing at all — while the declaration says "required". The two disagree, and the ones written in the defensive style are the ones the code actually obeys.

Why this is an observation and not a defect

No user meets it. Every real caller passes a data source, and the declaration being stricter than the implementation fails safe: it can only reject a call the component would have survived, never accept one it cannot. It cost one test fixture a line, and that line is now written.

It is worth a card anyway because the mismatch is the kind that decides a later question wrongly: someone reading dataSource: any as required will not know that a dataSource-less embed is a supported shape (and someone reading the guards will not know whether they are load-bearing or superstition). Both readings are currently defensible from the source, which is the actual problem.

If it is ever picked up

Two coherent resolutions, and the choice is a contract call, not execution:

  • Make it optional (dataSource?: DataSource) — matches the implementation, and lets a caller mount the wizard for its parse/mapping steps alone. Widens the accepted surface.
  • Keep it required and drop the guards — makes the implementation match the declaration, and turns the currently-silent "no capability" paths into something a caller can reason about. Narrows the code.

Either way any is worth replacing with DataSource: the component already casts to Partial< DataSource > at five sites to get any checking back, which is the declaration admitting the type it wants.

Refs #4040, #4297.

Activity

  1. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    Findings triage (first grade): HOLD — finding stays.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. added theissue type on Aug 17, 2026
  3. os-steve commented on Aug 17, 2026

    @os-steve
    Collaborator

    Concentrated finding round (unified triage seat, 2026-08-17): closed not planned. The declaration being stricter than the implementation fails safe (can only reject calls the component would survive), no user meets it, and the one cost it ever exacted — a test fixture line — is already paid and pinned. Re-declaring dataSource optional is a public-interface change on a published package with no consumer asking for it; the record of "the defensive reads are the real contract" is what this card is for, and it survives in the closed state.

    Reopen trigger: a second consumer trips on the required declaration, or a dataSource-less embed becomes a supported product shape worth declaring — then the fix is dataSource?: plus narrowing the internal reads, in one PR. Maintainer veto: reopen is free.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions