Exists-but-undefined overwrites Graphile Config option on merge
### 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 the `targetPreset`.

### Steps to reproduce

```ts
import { resolvePreset } from 'graphile-config'

console.log(
    JSON.stringify(
        resolvePreset({
            extends: [
                {
                    example: {
                        port: 4321,
                    },
                },
            ],
            example: {
                port: undefined,
            },
        }),
        null,
        2,
    ),
)

console.log(
    JSON.stringify(
        resolvePreset({
            extends: [
                {
                    example: {
                        port: 4321,
                    },
                },
            ],
            example: {},
        }),
        null,
        2,
    ),
)
```

### 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.

```sh
{
  "plugins": [],
  "disablePlugins": [],
  "lib": {},
  "example": {}
}
{
  "plugins": [],
  "disablePlugins": [],
  "lib": {},
  "example": {
    "port": 4321
  }
}
```

### Motivating Example

I'm working on my [graphile-config-example](https://github.com/jcgsville/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

```ts
import yargs from 'yargs'
import { hideBin } from 'yargs/helpers'
import { loadConfig } from 'graphile-config/load'
import { resolvePreset } from 'graphile-config'

import { run } from './run.js'

const argv = yargs(hideBin(process.argv))
    .option('config', {
        alias: 'C',
        description: 'The path to the config file',
        normalize: true,
    })
    .string('config')
    .option('port', {
        alias: 'P',
        description: 'The port on which the server will listen',
        normalize: true,
        type: 'number',
    })
    .number('port')
    .strict(true)
    .parseSync()

const main = async (): Promise<void> => {
    const userPreset = await loadConfig(argv.config)
    const argvPreset = extendUserPresetWithArgv(userPreset)
    const resolvedPreset = resolvePreset(argvPreset)

    await run(resolvedPreset)
}

const extendUserPresetWithArgv = (
    userPreset: GraphileConfig.Preset | null,
): GraphileConfig.Preset => ({
    extends: userPreset ? [userPreset] : [],
    example: {
        port: argv.port,
    },
})

main().catch((error) => {
    console.error(error)
    process.exit(1)
})
```

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.

```ts title=Alternative Implementations
// This works as I want it to
const extendUserPresetWithArgv = (
    userPreset: GraphileConfig.Preset | null,
): GraphileConfig.Preset => {
    const options: GraphileConfig.ExampleOptions = {}

    if (argv.port !== undefined) {
        options.port = argv.port
    }

    return {
        extends: userPreset ? [userPreset] : [],
        example: options,
    }
}

// This would also work
const extendUserPresetWithArgv = (
    userPreset: GraphileConfig.Preset | null,
): GraphileConfig.Preset => ({
    extends: userPreset ? [userPreset] : [],
    example: {
        port: argv.port ?? userPreset?.example?.port,
    },
})

// The `??=` operator also sets the value to undefined, so this also doesn't work:
const extendUserPresetWithArgv = (
    userPreset: GraphileConfig.Preset | null,
): GraphileConfig.Preset => {
    const options: GraphileConfig.ExampleOptions = {}
    options.port ??= argv.port

    return {
        extends: userPreset ? [userPreset] : [],
        example: options,
    }
}
```

### 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:

https://github.com/graphile/crystal/blob/dd081e8ecf0be5e04f77c9f28e2d37452bd0b89f/utils/graphile-config/src/resolvePresets.ts#L384

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](https://star.graphile.org/graphile-config/preset#extendpresetbasepreset-extensionpreset)