Skip to content

fix(firestore)!: decode missing keys for Optional and server-stamped fields - #85

Merged
fwal merged 4 commits into
mainfrom
fwal/pensive-feynman-kv3hni
Sep 14, 2026
Merged

fwal merged 4 commits into
mainfrom
fwal/pensive-feynman-kv3hni

Conversation

@fwal

@fwal fwal commented Sep 14, 2026

Copy link
Copy Markdown
Owner

Fixes a regression from the Effect v4 port: Firestore.Optional database variants rejected a missing document key with SchemaError: Missing key, so getById/queries/triggers failed on real documents that omit optional fields.

Changes

  • Optional select/insert/update use Schema.OptionFromOptionalNullOr(s, { onNoneEncoding: null }). Missing key, null and undefined decode to Option.none(); Option.none() still encodes as null.
  • OptionalDeletable.update uses OptionFromOptional(Union([s, DeleteInstance])): decodes a missing key, encodes Option.none() as an omitted key instead of undefined. Its fieldEvolve overload type is corrected to match runtime.
  • DateTimeInsert / DateTimeUpdate / ServerDateTime insert/update variants target Schema.optional(DateTimeUtc) and encode via SchemaGetter.transformOptional, so the key can be omitted for a server timestamp. createdAt: undefined is no longer required.
  • Regression test for models spreading struct fields with a decoding default (Schema.optionalKey(s).pipe(Schema.withDecodingDefault(...)), the v4 form of optionalWith(..., { default })).
  • MIGRATION.md and AGENTS.md updated.

Breaking (type-only)

Encoded type of Optional fields moves from NullishOr<S> to optional(NullOr<S>); the key becomes optional in Model.Encoded. Annotations using Schema.OptionFromNullishOr<...> must switch to Schema.OptionFromOptionalNullOr<...>. A merge update whose only field is Option.none() on an OptionalDeletable now fails invalid-argument (empty payload).

Closes #29

🤖 Generated with Claude Code

https://claude.ai/code/session_01UTvaTdYRhJ8MdaKxQ19miS


Generated by Claude Code

fwal and others added 2 commits September 14, 2026 10:34
…fields

`Firestore.Optional` database variants now use
`Schema.OptionFromOptionalNullOr`, so a document without the field decodes
to `Option.none()` again instead of failing with `SchemaError: Missing key`
under Effect v4. `Option.none()` is still written as `null`.

BREAKING CHANGE: the encoded type of `Optional` fields moves from
`NullishOr<S>` to `optional(NullOr<S>)`, making the key optional in
`Model.Encoded`; annotations using `Schema.OptionFromNullishOr<...>` must
switch to `Schema.OptionFromOptionalNullOr<...>`.

Also:
- `OptionalDeletable.update` decodes a missing key and encodes
  `Option.none()` as an omitted key rather than `undefined`.
- `DateTimeInsert`/`DateTimeUpdate`/`ServerDateTime` insert/update variants
  accept an omitted key for the server timestamp, so `createdAt: undefined`
  is no longer required.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UTvaTdYRhJ8MdaKxQ19miS
Regression test for models built from a struct whose field carries a
decoding default, and a MIGRATION note for the Effect v4 replacement of
`Schema.optionalWith(s, { default })`.

Closes #29

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UTvaTdYRhJ8MdaKxQ19miS
@github-actions github-actions Bot added 📖 docs Improvements or additions to documentation 📦 core labels Sep 14, 2026
@fwal fwal self-assigned this Sep 14, 2026
@fwal fwal added this to the 1.0 milestone Sep 14, 2026
@greptile-apps

greptile-apps Bot commented Sep 14, 2026

Copy link
Copy Markdown

Greptile Summary

This PR restores missing-key decoding for optional Firestore fields and allows server-stamped fields to be omitted from write inputs.

  • Optional decodes missing keys, null, and undefined to Option.none() while continuing to encode None as null.
  • OptionalDeletable preserves omitted keys through nested update encoding.
  • Regression tests cover optional fields, server timestamps, nested updates, and decoding defaults.
  • Changes since the previous review clarify when to use Schema.optionalKey for plain optional fields.

Confidence Score: 5/5

The PR appears safe to merge, with no new actionable findings and the previous nested-update omission issue fixed.

The nested leaf encoder now distinguishes an omitted property from a present value, and payload assembly skips omitted leaves. Regression coverage includes dotted updates, merge updates, and deletion. The documentation-only changes since the previous review introduce no established blocking failure.

Important Files Changed

Filename Overview
packages/effect-firebase/src/lib/firestore/model/optional.ts Updates optional-field schemas and types to support missing database keys.
packages/effect-firebase/src/lib/firestore/model/datetime.ts Encodes omitted server-stamped fields as server timestamp sentinels.
packages/effect-firebase/src/lib/firestore/model/repository.ts Preserves encoded key presence and skips omitted nested update leaves, addressing the previous finding.
packages/effect-firebase/src/lib/firestore/model/repository.spec.ts Adds regression coverage for nested omission, delete sentinels, and spread decoding defaults.
packages/effect-firebase/AGENTS.md Clarifies optional-key declarations and decoding-default usage.
packages/effect-firebase/MIGRATION.md Documents changed optional-field types, omission semantics, and plain optional-field guidance.

Reviews (3): Last reviewed commit: "docs(firestore): recommend Schema.option..." | Re-trigger Greptile

Comment thread packages/effect-firebase/src/lib/firestore/model/optional.ts
fwal and others added 2 commits September 14, 2026 12:40
The per-path leaf encoder read the encoded property back unconditionally,
so a nested `OptionalDeletable` given `Option.none()` produced a key with
an `undefined` value that the Firebase SDKs reject. The encoder now
reports key presence and the update loop skips omitted leaves.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UTvaTdYRhJ8MdaKxQ19miS
@fwal
fwal merged commit 2a0fcc6 into main Sep 14, 2026
6 checks passed
@fwal
fwal deleted the fwal/pensive-feynman-kv3hni branch September 14, 2026 12:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📦 core 📖 docs Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Schema using optionalWith produce Unsupported Schema Error

1 participant