Skip to content

Commit 6d0d0fc

Browse files
boneskullclaude
andcommitted
refactor(types): improve transform type inference
- Add const modifiers to bargs/bargsAsync generics for literal inference - Use conditional type for transforms property to allow both explicit TTransforms and default undefined cases - Refactor transform runners to use function overloads eliminating 8 type assertions - Remove as const from example transforms return type - Export runTransforms/runSyncTransforms for testing - Add comprehensive transforms test suite The transforms property now uses: transforms?: [TTransforms] extends [undefined] ? TransformsConfig<any, any, any, any> : TTransforms This allows transforms to work without type assertions when TTransforms defaults to undefined, while preserving full type safety when explicitly specified. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent cd2dcda commit 6d0d0fc

10 files changed

Lines changed: 593 additions & 265 deletions

File tree

examples/transforms.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ const main = async () => {
8080
}
8181
return true;
8282
});
83-
return [validFiles] as const;
83+
return [validFiles];
8484
},
8585
},
8686
handler: ({ values, positionals }) => {

src/bargs.ts

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -166,14 +166,8 @@ const handleHelpError = (
166166
export function bargs<
167167
const TOptions extends OptionsSchema,
168168
const TPositionals extends PositionalsSchema,
169-
const TTransforms extends
170-
| TransformsConfig<
171-
InferOptions<TOptions>,
172-
any,
173-
InferPositionals<TPositionals>,
174-
any
175-
>
176-
| undefined = undefined,
169+
const TTransforms extends TransformsConfig<any, any, any, any> | undefined =
170+
undefined,
177171
>(
178172
config: BargsConfig<TOptions, TPositionals, undefined, TTransforms>,
179173
options?: BargsOptions,
@@ -265,16 +259,10 @@ export function bargs(
265259
* Main bargs entry point for simple CLIs (no commands) - async version.
266260
*/
267261
export async function bargsAsync<
268-
TOptions extends OptionsSchema,
269-
TPositionals extends PositionalsSchema,
270-
TTransforms extends
271-
| TransformsConfig<
272-
InferOptions<TOptions>,
273-
any,
274-
InferPositionals<TPositionals>,
275-
any
276-
>
277-
| undefined = undefined,
262+
const TOptions extends OptionsSchema,
263+
const TPositionals extends PositionalsSchema,
264+
const TTransforms extends TransformsConfig<any, any, any, any> | undefined =
265+
undefined,
278266
>(
279267
config: BargsConfig<TOptions, TPositionals, undefined, TTransforms>,
280268
options?: BargsOptions,
@@ -290,8 +278,8 @@ export async function bargsAsync<
290278
* Main bargs entry point for command-based CLIs - async version.
291279
*/
292280
export async function bargsAsync<
293-
TOptions extends OptionsSchema,
294-
TCommands extends Record<string, CommandConfigInput>,
281+
const TOptions extends OptionsSchema,
282+
const TCommands extends Record<string, CommandConfigInput>,
295283
>(
296284
config: BargsConfigWithCommands<TOptions, TCommands>,
297285
options?: BargsOptions,
@@ -307,7 +295,9 @@ export async function bargsAsync(
307295
config: BargsConfig<
308296
OptionsSchema,
309297
PositionalsSchema,
310-
Record<string, CommandConfigInput> | undefined
298+
Record<string, CommandConfigInput> | undefined,
299+
| TransformsConfig<unknown, unknown, readonly unknown[], readonly unknown[]>
300+
| undefined
311301
>,
312302
options?: BargsOptions,
313303
): Promise<BargsResult<unknown, readonly unknown[], string | undefined>> {
@@ -333,14 +323,7 @@ export async function bargsAsync(
333323
});
334324

335325
// Run transforms if present (type-erased in implementation)
336-
const transforms = config.transforms as
337-
| TransformsConfig<
338-
unknown,
339-
unknown,
340-
readonly unknown[],
341-
readonly unknown[]
342-
>
343-
| undefined;
326+
const transforms = config.transforms;
344327
const transformed = transforms
345328
? await runTransforms(transforms, parsed.values, parsed.positionals)
346329
: { positionals: parsed.positionals, values: parsed.values };

src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,19 +80,25 @@ export type {
8080
CommandConfigInput,
8181
CountOption,
8282
EnumOption,
83+
EnumPositional,
8384
Handler,
8485
HandlerFn,
8586
InferOption,
8687
InferOptions,
8788
InferPositional,
8889
InferPositionals,
90+
InferTransformedPositionals,
91+
InferTransformedValues,
8992
NumberOption,
9093
NumberPositional,
9194
OptionDef,
9295
OptionsSchema,
9396
PositionalDef,
9497
PositionalsSchema,
98+
PositionalsTransformFn,
9599
StringOption,
96100
StringPositional,
101+
TransformsConfig,
102+
ValuesTransformFn,
97103
VariadicPositional,
98104
} from './types.js';

src/parser.ts

Lines changed: 117 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,43 @@ export const runHandler = async <T>(
5959
await handler(result);
6060
};
6161

62+
// ─── Sync Transform Runner ──────────────────────────────────────────────────
63+
64+
/**
65+
* Run transforms synchronously when transforms are defined.
66+
*/
67+
export function runSyncTransforms<
68+
TValuesIn,
69+
TValuesOut,
70+
TPositionalsIn extends readonly unknown[],
71+
TPositionalsOut extends readonly unknown[],
72+
>(
73+
transforms: TransformsConfig<
74+
TValuesIn,
75+
TValuesOut,
76+
TPositionalsIn,
77+
TPositionalsOut
78+
>,
79+
values: TValuesIn,
80+
positionals: TPositionalsIn,
81+
): { positionals: TPositionalsOut; values: TValuesOut };
82+
83+
/**
84+
* Pass through unchanged when transforms are undefined.
85+
*/
86+
export function runSyncTransforms<
87+
TValues,
88+
TPositionals extends readonly unknown[],
89+
>(
90+
transforms: undefined,
91+
values: TValues,
92+
positionals: TPositionals,
93+
): { positionals: TPositionals; values: TValues };
94+
6295
/**
6396
* Run transforms synchronously. Throws if any transform returns a thenable.
6497
*/
65-
export const runSyncTransforms = <
98+
export function runSyncTransforms<
6699
TValuesIn,
67100
TValuesOut,
68101
TPositionalsIn extends readonly unknown[],
@@ -73,40 +106,80 @@ export const runSyncTransforms = <
73106
| undefined,
74107
values: TValuesIn,
75108
positionals: TPositionalsIn,
76-
): { positionals: TPositionalsOut; values: TValuesOut } => {
77-
let currentValues: unknown = values;
78-
let currentPositionals: unknown = positionals;
79-
80-
if (transforms?.values) {
81-
const result = transforms.values(currentValues as TValuesIn);
82-
if (isThenable(result)) {
83-
throw new BargsError(
84-
'Transform returned a thenable. Use bargsAsync() for async transforms.',
85-
);
86-
}
87-
currentValues = result;
109+
): {
110+
positionals: TPositionalsIn | TPositionalsOut;
111+
values: TValuesIn | TValuesOut;
112+
} {
113+
if (!transforms) {
114+
return { positionals, values };
88115
}
89116

90-
if (transforms?.positionals) {
91-
const result = transforms.positionals(currentPositionals as TPositionalsIn);
92-
if (isThenable(result)) {
93-
throw new BargsError(
94-
'Transform returned a thenable. Use bargsAsync() for async transforms.',
95-
);
96-
}
97-
currentPositionals = result;
98-
}
117+
// Apply values transform
118+
const transformedValues = transforms.values
119+
? (() => {
120+
const result = transforms.values(values);
121+
if (isThenable(result)) {
122+
throw new BargsError(
123+
'Transform returned a thenable. Use bargsAsync() for async transforms.',
124+
);
125+
}
126+
return result;
127+
})()
128+
: values;
129+
130+
// Apply positionals transform
131+
const transformedPositionals = transforms.positionals
132+
? (() => {
133+
const result = transforms.positionals(positionals);
134+
if (isThenable(result)) {
135+
throw new BargsError(
136+
'Transform returned a thenable. Use bargsAsync() for async transforms.',
137+
);
138+
}
139+
return result;
140+
})()
141+
: positionals;
99142

100143
return {
101-
positionals: currentPositionals as TPositionalsOut,
102-
values: currentValues as TValuesOut,
144+
positionals: transformedPositionals,
145+
values: transformedValues,
103146
};
104-
};
147+
}
148+
149+
// ─── Async Transform Runner ─────────────────────────────────────────────────
150+
151+
/**
152+
* Run transforms asynchronously when transforms are defined.
153+
*/
154+
export function runTransforms<
155+
TValuesIn,
156+
TValuesOut,
157+
TPositionalsIn extends readonly unknown[],
158+
TPositionalsOut extends readonly unknown[],
159+
>(
160+
transforms: TransformsConfig<
161+
TValuesIn,
162+
TValuesOut,
163+
TPositionalsIn,
164+
TPositionalsOut
165+
>,
166+
values: TValuesIn,
167+
positionals: TPositionalsIn,
168+
): Promise<{ positionals: TPositionalsOut; values: TValuesOut }>;
169+
170+
/**
171+
* Pass through unchanged when transforms are undefined.
172+
*/
173+
export function runTransforms<TValues, TPositionals extends readonly unknown[]>(
174+
transforms: undefined,
175+
values: TValues,
176+
positionals: TPositionals,
177+
): Promise<{ positionals: TPositionals; values: TValues }>;
105178

106179
/**
107180
* Run transforms asynchronously.
108181
*/
109-
export const runTransforms = async <
182+
export async function runTransforms<
110183
TValuesIn,
111184
TValuesOut,
112185
TPositionalsIn extends readonly unknown[],
@@ -117,25 +190,29 @@ export const runTransforms = async <
117190
| undefined,
118191
values: TValuesIn,
119192
positionals: TPositionalsIn,
120-
): Promise<{ positionals: TPositionalsOut; values: TValuesOut }> => {
121-
let currentValues: unknown = values;
122-
let currentPositionals: unknown = positionals;
123-
124-
if (transforms?.values) {
125-
currentValues = await transforms.values(currentValues as TValuesIn);
193+
): Promise<{
194+
positionals: TPositionalsIn | TPositionalsOut;
195+
values: TValuesIn | TValuesOut;
196+
}> {
197+
if (!transforms) {
198+
return { positionals, values };
126199
}
127200

128-
if (transforms?.positionals) {
129-
currentPositionals = await transforms.positionals(
130-
currentPositionals as TPositionalsIn,
131-
);
132-
}
201+
// Apply values transform (await if needed)
202+
const transformedValues = transforms.values
203+
? await transforms.values(values)
204+
: values;
205+
206+
// Apply positionals transform (await if needed)
207+
const transformedPositionals = transforms.positionals
208+
? await transforms.positionals(positionals)
209+
: positionals;
133210

134211
return {
135-
positionals: currentPositionals as TPositionalsOut,
136-
values: currentValues as TValuesOut,
212+
positionals: transformedPositionals,
213+
values: transformedValues,
137214
};
138-
};
215+
}
139216

140217
/**
141218
* Build parseArgs options config from our options schema.

src/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ export interface BargsConfig<
8282
* Values transform receives InferOptions<TOptions>, positionals transform
8383
* receives InferPositionals<TPositionals>.
8484
*/
85-
transforms?: TTransforms;
85+
transforms?: [TTransforms] extends [undefined]
86+
? TransformsConfig<any, any, any, any>
87+
: TTransforms;
8688
version?: string;
8789
}
8890

0 commit comments

Comments
 (0)