I want to pick all the optional properties of a nested object type.
Example:
// Options that the user passes. He must use 'a.b.mandatory'.
type Options = {
a: {
b: {
mandatory: number;
c?: {
d?: string;
e: string;
f?: string;
}
}
}
};
// The default options are for the non-mandatory parts of 'Options'.
// If the user doesn't provide 'c' then I want to provide default options for it.
// See that 'e' is mandatory like in Options because when providing 'c',
// 'e' must be provided as well.
type DefaultOptions = BlackBox<Options>;
// -->
// {
// a: {
// b: {
// c: {
// d?: string;
// e: string;
// f?: string;
// }
// }
// }
// };
Then, I can set my default options like so:
const defaultOptions: DefaultOptions = {
a: {
b: {
c: {
d: 'default value',
e: 'mandatory-default value'
}
}
}
};
This is for later merge using a tool like [defu][1] to get the final options the user can safely work with:
function f(options: Options) {
const optionsWithDefaults = defu(options, defaultOptions);
}
I want everything to be generically typed.
Edit: Added clarification with an example
type LambdaOptions = {
aws: {
lambda: {
name: string;
version: string;
};
};
middy?: {
middlewares?: MiddlewareObject[];
};
sentry: {
dsn: string;
tracesSampleRate?: number;
rethrowAfterCapture?: boolean;
};
};
const defaultOptions: DefaultOptions<LambdaOptions> = {
sentry: {
tracesSampleRate: 0.1
}
};
const options: Options = {...};
const optionsWithDefaults = defu(options, defaultOptions);
// -->
// {
// aws: {
// lambda: {
// name: string;
// version: string;
// };
// };
// middy?: {
// middlewares?: MiddlewareObject[];
// };
// sentry: {
// dsn: string;
// tracesSampleRate: number;
// rethrowAfterCapture?: number;
// };
// };
// sentry.tracesSampleRate is protected by a default value
[1]: https://github.com/unjs/defu
I've implemented something similar a while ago.
For a nice type to help with declaring your default config:
Then you can define your default config with intellisense like:
For the resulting type to be correct:
What you want to achieve can be done with a merge function returning a intersection of the types like.
Since the result is A & B, B will overwrite A's optional statement if the same key is given without it.
You can check this out for a working example: https://github.com/Aderinom/typedconf/blob/master/src/config.builder.ts#L118
Edit: Changed incorrect wording (union to intersection) based on jcalz comment