-
-
Notifications
You must be signed in to change notification settings - Fork 575
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Exists-but-undefined overwrites Graphile Config option on merge #2286
Comments
If this were made the case, we couldn’t “unset” something that was set, e.g. so that a default could be used. This may be particularly important for organisations that create a base shared preset, but now someone wants to override one of the options to use the library defaults (without having to keep them in sync). It’s relatively easy (though not particularly ergonomic) to not set keys that are undefined by using spread ternaries; or a helper function could be used to strip undefineds from an object. I’m unconvinced that this is a sufficiently desirable feature purely for ergonomics to justify preventing a capability that cannot otherwise be expressed. |
Couldn't someone unset a feature by setting it to null? I guess, now that I'm thinking deeply about it, that would require the library author to explicitly include null as an allowable type in the options they define. Maybe even that requirement outweighs the minor ergonomic benefit I suggested |
Continuing to reflect on this. To some degree, I actually like requiring library authors to make Take for example, a module that accepts a declare global {
namespace GraphileConfig {
interface ExampleOptions {
port?: number | undefined
}
interface Preset {
example?: ExampleOptions
}
}
} Then, for other options where it would be useful for users to unset it, you can use |
Also, didn't mean to label as a bug. Looks like I can't remove label though |
Setting null and unsetting are two different things in my opinion, for example const {
port = parseInt(process.env.PORT, 10) || 5678,
maskError = defaultMaskError
} = preset.scope || {} |
All config options are optional, thus all must have some kind of default in the library; undefined lets you reference the default without having to look up what it is. |
I see. In my opinion, I think that's a bit on the confusing side. I'm not sure there's a strong use case for "unset the value from the preset but set it back to the library default". I think most people are going to think of extending a preset as using a default. Wheras this behavior makes library users have to consider multiple layers of defaults, and the ability to remove the layer above you in the tree is a bit weird. But you've certainly thought longer about what postgraphile/grafast/etc users need than I have 🙂, and I guess JS forces our hand here and there's no choice here that does not involve some documentation of expected behavior and reliance on library authors to conform to patterns :) (PS I really like that Rust got rid of null 😅. Explicitness ftw! JS is the polar opposite with 3x versions of it haha) So if I understand correctly, we want to prioritize the experience for preset creators vs library authors when those are in tension - I think this is the correct call Thus, if I'm a library user creating a preset:
The downside of this is that library authors need to be a bit more careful when they're creating & extending presets programmatically from things like CLI arguments or default values. I'll see if there's a good way to incorporate a bit more clarity on this into the docs |
Yes that sounds like an accurate summary. Essentially it follows the behaviour of |
Summary
Graphile Config's mergePreset() overwrites options when an option in
sourcePreset
exists but is undefined. I think it would make more sense if an option that exists but is undefined did not override the option in thetargetPreset
.Steps to reproduce
Expected v Actual
These two console logs print out the following. I wish they resolved to the same preset, and thus printed out the same thing.
Motivating Example
I'm working on my graphile-config-example project for library authors. I think this change would make it simpler for library authors to have presets constructed from multiple different places like env vars, CLI args, or a provided config file and merge them. Here's the code that I wrote
My extendUserPresetWithArgv is overwriting the userPreset. I could modify presetFromArgv to avoid this, but it would be nice for library authors to not have to worry about this.
Possible Solution
If an option exists but undefined (i.e.
{ port: undefined }
, then it should not overwrite the corresponding property in the targetPreset.I propose that exists-but-null be the way that users indicate "I'm trying to overwrite the preset I'm extending but explicitly disabling this option".
I could see the argument for the other side. Maybe the existence of the property driving overwrite behavior is a bit clearer / simpler behavior, but I do think it makes dev ergonomics a bit worse.
I'm willing to make the change to graphile config if we agree that this is a good change to make 🙂 It should be a simple change somewhere around here:
crystal/utils/graphile-config/src/resolvePresets.ts
Line 384 in dd081e8
It has the potential to be a bit dangerous for existing graphile libraries that use graphile config. Wouldn't surprise me if some behavior would subtly change.
Even if we decide not to make a change, I'd like to make an addition to Graphile Config docs to call this out explicitly in the pseudocode for extend preset
The text was updated successfully, but these errors were encountered: