fix(api): type day_summary, label and sleep type as nullable - #23
Merged
Conversation
The Oura OpenAPI spec (verified against 1.37) declares three fields the CLI reads as nullable, none of them required: daily_stress.day_summary anyOf[PublicDailyStressSummary, null] workout.label anyOf[string, null] sleep.type anyOf[PublicSleepType, null] All three were declared `string`. Nothing breaks at runtime today — every backing column is nullable TEXT, and importDaily already wrote `w.label ?? ''` against exactly this case — but the declared contract told callers a value is always present, so any new reader could reasonably assume one. Types only; no behaviour change. The added test drives importDaily with nulls in all three fields and asserts what actually lands in SQLite, including the existing '' coercion for workout labels. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DRnTxyiiF3XHiyFqZJ3soz
Greptile SummaryThe PR aligns three API response fields with the upstream nullable and optional contract.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; the previously reported required-property mismatch is fixed by marking all three fields optional as well as nullable.
|
| Filename | Overview |
|---|---|
| src/api/types.ts | The three fields are now both optional and nullable, fully addressing the previously reported mismatch with responses that omit them. |
| src/db/import.ts | Optional stress summaries and sleep types are normalized to SQL NULL, while existing workout-label normalization remains unchanged. |
| src/db/import.test.ts | Tests cover explicit null values and omitted properties through the complete import path. |
| CHANGELOG.md | The changelog accurately describes the corrected types and import normalization. |
Reviews (2): Last reviewed commit: "fix(api): mark the three fields optional..." | Re-trigger Greptile
Greptile review: the spec leaves day_summary, label and type out of each schema's `required` list, so the property can be absent, not only null. `string | null` on a required property does not express that. Making them optional surfaced a latent gap tsc had no way to see before: `undefined` is not a member of bun:sqlite's SQLQueryBindings, so the two insert sites were passing a value the binding type rejects. The runtime coerces undefined to NULL, so nothing was breaking — but it was leaning on an undocumented coercion. Both sites now map to null explicitly. Adds a test driving importDaily with the properties omitted entirely, alongside the existing explicit-null case. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DRnTxyiiF3XHiyFqZJ3soz
drakulavich
added a commit
that referenced
this pull request
Jul 25, 2026
Greptile review: the file described the three nullability findings as "Fixed in #23" while this branch still carries the old declarations, so a reader landing on this commit would see the record and the code disagree. Reworded to "filed", with the open state called out explicitly. Also records that all three are absent from their schema's `required` list — they can be omitted, not merely null — and drops the duplicate mention of that in the paragraph above the table. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DRnTxyiiF3XHiyFqZJ3soz
drakulavich
added a commit
that referenced
this pull request
Jul 26, 2026
An independent `omc ask grok` pass checked every claim against the
source. All verified before changing anything.
Corrected:
- "each command wires handleError in its own catch" is false: only the
data-path commands do. healthcheck deliberately swallows into
{ ok: false, error } as a probe, and login/describe/manifest have no
catch at all. Stated the real shape, including that index.ts has no
global handler.
- docs/ARCHITECTURE.md is stale on layering too, not just citty — its
diagram makes api and db peers when db imports api, and it omits the
root-level format modules. Withdrew the "accurate on layering"
endorsement.
- npm audit is not a gate: the step swallows failures into a
::warning::. Also noted release.yml runs no tsc.
- Dropped the invented "no default exports outside src/index.ts"
exception; there are no default exports in src/ at all.
Added, all non-obvious enough that an agent would get them wrong:
- Registering a command touches four files. Missing the SUBCOMMANDS set
in src/lib/argv-normalize.ts silently breaks `--format json <cmd>`,
because citty does not hoist root flags and that normalizer is what
moves them. The describe/manifest agent surface is hard-coded too.
- Schema migrations are append-only (`version > current`), so editing a
shipped migration is a no-op on existing databases.
- Oura API fields should be treated as nullable — #23 retyped three of
them after upstream drift.
- Both output modes apply to new data commands; the JSON-only and
interactive commands are deliberate and must not be "fixed".
- The OURA_TOKEN / OURA_TOKEN_PATH / OURA_DB_PATH / OURA_TZ / NO_COLOR
environment surface, which the file omitted entirely.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DRnTxyiiF3XHiyFqZJ3soz
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Found by an
api-drift-watcherrun against the live Oura spec — the first run able to reachcloud.ouraring.com(the egress block recorded indocs/loops/api-drift-watcher-state.mdis gone).Finding
Three fields the CLI reads are nullable in the spec and were declared
string:daily_stress.day_summaryanyOf[PublicDailyStressSummary, null]stringstring | nullworkout.labelanyOf[string, null]stringstring | nullsleep.typeanyOf[PublicSleepType, null]stringstring | nullNone is in the schema's
requiredlist.Impact
No runtime breakage today. Every backing column is nullable
TEXT(daily_stress.day_summary,workouts.label,sleep_model.type), so a null inserts cleanly, andqueries.tsalready readsday_summaryback asstring | null.importDailyeven writesw.label ?? ''— defensive code that the old type made look dead.The problem is the contract: the declared types promised a value that the API does not guarantee, so a new reader could reasonably skip a null check and
tscwould agree with them.Change
Types only, no behaviour change.
bunx tsc --noEmitpasses with no downstream edits needed, which confirms nothing was relying on the non-null claim in a way that breaks.The new test drives
importDailythrough a stub client returning nulls in all three fields and asserts what actually lands in SQLite.One thing worth a decision (not changed here)
importDailycoerces a null workout label to''rather than storingNULL, which collapses "no label" and "empty label" into the same value. The test pins the current behaviour rather than changing it — happy to switch it toNULLin a separate PR if you'd prefer the distinction preserved.Not in scope
day_summaryandtypeare enums upstream (restored|normal|stressfulanddeleted|sleep|long_sleep|late_nap|rest), still typed as openstring. Narrowing them is a separate change.Verification