Skip to content

Commit 0c771d2

Browse files
committed
Update type documentation
1 parent a1cd236 commit 0c771d2

1 file changed

Lines changed: 63 additions & 8 deletions

File tree

src/types/CONVERTING_ATTRIBUTES.md

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,19 @@ export default attributes;
4949

5050
Three things to notice:
5151

52-
- **`@generates ModeBar`** — declares the canonical public type name. The
53-
`.d.ts` generator uses this to name the output.
52+
- **`@generates ModeBar`** — declares the public type name. The `.d.ts`
53+
generator uses this to name the output. Naming conventions:
54+
55+
| Category | Convention | Examples |
56+
|---|---|---|
57+
| Components | PascalCase of the concept | `ModeBar`, `ColorBar`, `Slider`, `RangeSelector` |
58+
| Traces | `<TraceName>Data` | `PieData`, `SankeyData`, `CandlestickData` |
59+
| Layout | `Layout` | |
60+
61+
If the generated type needs hand-written refinements, use a `*Generated`
62+
suffix instead (e.g. `@generates ModeBarGenerated`) so the hand-written
63+
file can extend it under the original public name. See
64+
[Layering hand-written refinements](#layering-hand-written-refinements).
5465
- **`as const satisfies AttributeMap`**`as const` preserves literal types
5566
like `values: ['v', 'h']`; `satisfies AttributeMap` validates structure
5667
without widening.
@@ -118,10 +129,10 @@ export interface ModeBar {
118129
export type { ModeBar } from '../generated/components/modebar';
119130
```
120131

121-
If the hand-written type was richer than the schema (e.g. used a narrowed
122-
union where the schema says `string`), document the gap in a comment or
123-
file an issue. Do not silently lose ergonomicseither improve the schema
124-
(add `values: [...]`) or layer a hand-written refinement on top.
132+
If the hand-written type was richer than the schema (e.g. a narrowed union
133+
where the schema says `string`, or extra non-schema fields), don't silently
134+
lose thosesee
135+
[Layering hand-written refinements](#layering-hand-written-refinements).
125136

126137
### 7. Verify
127138

@@ -158,8 +169,7 @@ for the canonical example. The full conversion changed:
158169
(with `as const satisfies AttributeMap` and `@generates ModeBar`)
159170
- `.default` added to `require('./attributes')` in `index.js` and `defaults.js`
160171
- The hand-written `ModeBar` interface in `src/types/core/layout.d.ts` was
161-
removed and replaced with
162-
`export type { ModeBar } from '../generated/components/modebar';`
172+
replaced with a re-export from the generated file
163173

164174
Schema output verified byte-identical (2547 bytes) before and after the
165175
conversion.
@@ -192,6 +202,51 @@ based on an older schema. Order of operations:
192202
4. **If the schema has fields the hand-written type lacked**, that's a free
193203
coverage win — accept the generated type.
194204

205+
## Layering hand-written refinements
206+
207+
When the generated type is complete, a simple re-export is all you need
208+
(see step 6). But when the hand-written type was richer than the schema —
209+
narrower unions, extra non-schema fields, etc. — use the `*Generated`
210+
suffix pattern so the hand-written file can extend the generated type.
211+
212+
First, change the `@generates` tag to use a `*Generated` suffix:
213+
214+
```ts
215+
// src/components/modebar/attributes.ts
216+
/**
217+
* @generates ModeBarGenerated
218+
*/
219+
```
220+
221+
Then in the hand-written file, extend it under the original public name.
222+
The `extends` brings all schema-derived fields along; the body only
223+
contains what the schema can't express:
224+
225+
```ts
226+
// src/types/core/layout.d.ts
227+
import type { ModeBarGenerated } from '../generated/components/modebar';
228+
229+
export interface ModeBar extends ModeBarGenerated {
230+
// Schema says `string`; narrow to the known button set.
231+
remove: ModeBarDefaultButtons | ModeBarDefaultButtons[];
232+
233+
// Not in the schema — runtime-only convenience field.
234+
_buttons?: HTMLElement[];
235+
}
236+
```
237+
238+
Consumers still write `import type { ModeBar } from 'plotly.js'` — the
239+
`*Generated` intermediate is an internal detail.
240+
241+
**When to refine vs. when to fix the schema:**
242+
243+
- If a field's type is too wide (e.g. `string` instead of a union), prefer
244+
adding `values: [...]` to the schema so the generated type is correct by
245+
construction and the simple re-export path works.
246+
- If the refinement is about non-schema concerns (runtime-only fields,
247+
narrowing for DX, overloaded method signatures), use the `*Generated`
248+
extend pattern.
249+
195250
## Order of conversion (for parallel work)
196251

197252
Pick from this priority list. Lower-numbered items are smaller / simpler.

0 commit comments

Comments
 (0)