-
-
Notifications
You must be signed in to change notification settings - Fork 7
fix(schema): drop constructor defaults when deriving update schemas #814
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
Open
MindfulLearner
wants to merge
11
commits into
main
Choose a base branch
from
feat-omit-default-removal
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
c2e26d7
feat(): added implementation, api test and story test, and added comm…
MindfulLearner 22972a1
lintfix
MindfulLearner 7698801
refactor(schema): replace omitCostructorDefaultswith composable dropC…
MindfulLearner 26940e6
Merge remote-tracking branch 'origin/main' into feat-omit-default-rem…
MindfulLearner c9c9c67
Merge remote-tracking branch 'origin/main' into feat-omit-default-rem…
MindfulLearner 18e7a1e
chore(): changes following reviews
MindfulLearner df03d15
Merge remote-tracking branch 'origin/main' into feat-omit-default-rem…
MindfulLearner 44373be
rebuild
MindfulLearner f311ff0
fix(dropconstructor): clear rebuild, annotate,annotatekey,check defau…
MindfulLearner 8f5f9a4
chore(test): testing if polymorphic this and this returning get dropped
MindfulLearner 1f18526
lint
MindfulLearner File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| "effect-app": patch | ||
| --- | ||
|
|
||
| Add `Schema.dropConstructorDefault`, a `Struct.Lambda` that clears a field's `withConstructorDefault`. Compose it with `Struct.omit`/`Struct.map` when deriving a partial-update schema from a "create" schema, so omitted fields' constructor defaults don't resurrect themselves in `.make(...)` output. | ||
|
|
||
| Also, `makeExactOptional` now drops constructor defaults automatically, matching this behavior. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
131 changes: 131 additions & 0 deletions
131
packages/vue-components/stories/OmegaForm/OmitConstructorDefaults.vue
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| <template> | ||
| <h1>Does editing one field wipe the others?</h1> | ||
| <p> | ||
| Each column starts with a record already saved on the server: <code>name</code> and <code>age</code> both filled in. | ||
| The edit dialog below it only exposes <b>name</b>, a narrow edit action that never shows or touches <code | ||
| >age</code>. Click <b>Save</b> without touching <code>age</code> and watch what happens to it in the "Saved record" | ||
| box above. | ||
| </p> | ||
| <div style="display: flex; gap: 2rem; flex-wrap: wrap"> | ||
| <section style="flex: 1; min-width: 320px"> | ||
| <h2>Buggy: plain <code>Struct.omit</code></h2> | ||
|
|
||
| <h3>Saved record (server)</h3> | ||
| <pre>{{ buggyDb }}</pre> | ||
|
|
||
| <h3>Edit dialog (only "name" is editable here)</h3> | ||
| <buggyForm.Form> | ||
| <template #default> | ||
| <buggyForm.Input | ||
| label="name" | ||
| name="name" | ||
| /> | ||
| <v-btn type="submit"> | ||
| Save | ||
| </v-btn> | ||
| </template> | ||
| </buggyForm.Form> | ||
|
|
||
| <p | ||
| v-if="buggyResult" | ||
| :style="{ color: buggyResult.ageWiped ? 'crimson' : 'seagreen', fontWeight: 'bold' }" | ||
| > | ||
| {{ buggyResult.ageWiped ? "age was wiped by an update that never touched it!" : "age survived the update" }} | ||
| </p> | ||
|
|
||
| <h3>Payload sent to the server</h3> | ||
| <pre>{{ buggyPayload ?? "(not submitted yet)" }}</pre> | ||
| </section> | ||
|
|
||
| <section style="flex: 1; min-width: 320px"> | ||
| <h2>Fixed: <code>Struct.map(S.dropConstructorDefault)</code></h2> | ||
|
|
||
| <h3>Saved record (server)</h3> | ||
| <pre>{{ fixedDb }}</pre> | ||
|
|
||
| <h3>Edit dialog (only "name" is editable here)</h3> | ||
| <fixedForm.Form> | ||
| <template #default> | ||
| <fixedForm.Input | ||
| label="name" | ||
| name="name" | ||
| /> | ||
| <v-btn type="submit"> | ||
| Save | ||
| </v-btn> | ||
| </template> | ||
| </fixedForm.Form> | ||
|
|
||
| <p | ||
| v-if="fixedResult" | ||
| :style="{ color: fixedResult.ageWiped ? 'crimson' : 'seagreen', fontWeight: 'bold' }" | ||
| > | ||
| {{ fixedResult.ageWiped ? "age was wiped by an update that never touched it!" : "age survived the update" }} | ||
| </p> | ||
|
|
||
| <h3>Payload sent to the server</h3> | ||
| <pre>{{ fixedPayload ?? "(not submitted yet)" }}</pre> | ||
| </section> | ||
| </div> | ||
| </template> | ||
|
|
||
| <script setup lang="ts"> | ||
| import * as Effect from "effect-app/Effect" | ||
| import * as S from "effect-app/Schema" | ||
| import * as Struct from "effect/Struct" | ||
| import { ref } from "vue" | ||
| import { useOmegaForm } from "../../src" | ||
|
|
||
| // `age` carries a constructor default: the field that can leak. | ||
| // `name` is plain, no default: the field the edit dialog actually exposes. | ||
| const Person = S.Struct({ | ||
| name: S.NonEmptyString.pipe(S.optionalKey), | ||
| age: S.Finite.pipe(S.optionalKey, S.withConstructorDefault(Effect.succeed(0))) | ||
| }) | ||
|
|
||
| // The bug: fields carried over by Struct.omit keep their inherited constructor default. | ||
| const UpdatePersonBuggy = S.Struct(Struct.omit(Person.fields, [] as const)) | ||
|
|
||
| // The fix: same fields, constructor defaults stripped. | ||
| const UpdatePersonFixed = S.Struct(Struct.map(Struct.omit(Person.fields, [] as const), S.dropConstructorDefault)) | ||
|
|
||
| // Simulated server state: a record already saved before the edit dialog opens. | ||
| const buggyDb = ref<{ name: string; age: number }>({ name: "Acme Corp", age: 30 }) | ||
| const fixedDb = ref<{ name: string; age: number }>({ name: "Acme Corp", age: 30 }) | ||
|
|
||
| const buggyResult = ref<{ ageWiped: boolean }>() | ||
| const fixedResult = ref<{ ageWiped: boolean }>() | ||
|
|
||
| const buggyPayload = ref<unknown>() | ||
| const fixedPayload = ref<unknown>() | ||
|
|
||
| const buggyForm = useOmegaForm(UpdatePersonBuggy, { | ||
| onSubmit: async ({ value }) => { | ||
| const ageBefore = buggyDb.value.age | ||
| const payload = UpdatePersonBuggy.make(value) | ||
| buggyPayload.value = payload | ||
| // simulates the server applying the update payload as a partial merge (PATCH semantics) | ||
| buggyDb.value = { ...buggyDb.value, ...payload } | ||
| buggyResult.value = { ageWiped: buggyDb.value.age !== ageBefore } | ||
| } | ||
| }) | ||
|
|
||
| const fixedForm = useOmegaForm(UpdatePersonFixed, { | ||
| onSubmit: async ({ value }) => { | ||
| const ageBefore = fixedDb.value.age | ||
| const payload = UpdatePersonFixed.make(value) | ||
| fixedPayload.value = payload | ||
| fixedDb.value = { ...fixedDb.value, ...payload } | ||
| fixedResult.value = { ageWiped: fixedDb.value.age !== ageBefore } | ||
| } | ||
| }) | ||
| </script> | ||
|
|
||
| <style scoped> | ||
| h1 { | ||
| margin-bottom: 1rem; | ||
| } | ||
| h3 { | ||
| margin-top: 1rem; | ||
| } | ||
| </style> |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.