Skip to content

fix(downgrader): give patternProperties schemas additionalProperties: true in 3.0 - #101

Open
dinwwwh wants to merge 3 commits into
mainfrom
claude/project-thread-m9a0i3
Open

dinwwwh wants to merge 3 commits into
mainfrom
claude/project-thread-m9a0i3

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

Requested by dinwwwh · project thread

3.0 has no patternProperties, so the 3.1 to 3.0 converter removes it, along with any additionalProperties beside it (a schema or false there would reject the properties patternProperties allowed). What's left is often a bare { type: "object" }, and openapi-typescript types that as Record<string, never>. Code generators read a missing additionalProperties as "no other properties allowed".

This shows up in real output:

  • Zod z.looseRecord (patternProperties, no additionalProperties)
  • Effect 4 through @orpc/effect: Schema.Record(Schema.Number, V), template-literal key records and StructWithRest (additionalProperties: true), and records with onExcessProperty: "error" (additionalProperties: false)
  • patternProperties beside an additionalProperties schema, or beside properties
  • SendGrid's contacts/search/emails response

Now finishSchema sets additionalProperties: true whenever patternProperties is present. The meaning doesn't change: a missing additionalProperties already allowed every property, and the schema is still marked as having lost a restriction.

  • src/v3.1-to-v3.0.ts: the additionalProperties = true step in finishSchema. The field converter still drops the input additionalProperties, as on main.
  • README.md: the patternProperties row says additionalProperties becomes true.
  • tests/v3.1-to-v3.0.test.ts: the existing test checks additionalProperties set to undefined, true, false and a schema, plus a case with properties. It fails without the fix.

Checks: 262 tests pass and pnpm run check is clean.

Note: #100's test expects { propertyNames: { enum }, patternProperties } to give {}. With this change it gives { additionalProperties: true }, so whichever PR merges second needs that expectation updated.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FZoWKNpDyGBHkdaur4kKtA

…ties in 3.1 to 3.0

Effect 4 (via @orpc/effect) writes `additionalProperties: true` beside
`patternProperties` for records with number or template-literal keys and
for StructWithRest. Dropping it left a bare `{ type: object }`, which
openapi-typescript types as `Record<string, never>`. `true` allows every
property, so it never contradicts `patternProperties`; only schemas and
`false` are dropped now.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FZoWKNpDyGBHkdaur4kKtA
@dinwwwh dinwwwh self-assigned this Oct 24, 2026
@codecov

codecov Bot commented Oct 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed

codspeed Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 21 untouched benchmarks


Comparing claude/project-thread-m9a0i3 (8af4ceb) with main (6a906e9)

Open in CodSpeed

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — read the full diff and verified the new assertion is live: reverting the && item !== true guard makes the changed test fail, and it passes with the fix.

  • additionalProperties: true preserved beside patternProperties (packages/downgrader/src/v3.1-to-v3.0.ts:148): the guard now drops only a false or schema value, keeping the boolean true, which allows every property and so never rejects what the removed patternProperties allowed. A bare { type: "object" } no longer leaks through as Record<string, never>.
  • README row updated (packages/downgrader/README.md:193): the patternProperties row now states that a schema (or false) is dropped but additionalProperties: true stays.
  • Test extended (packages/downgrader/tests/v3.1-to-v3.0.test.ts:1103): the existing case now also covers the false-beside-patternProperties drop, a schema value ({ type: "number" } → {}), and the kept true.

The change stays safe under the surrounding logic: patternProperties remains in RESTRICTING_KEYWORDS, so the output is still marked loose; isUnevaluatedAdditional stays false while patternProperties is present; and an existing additionalProperties keeps losesRestriction false for unevaluatedProperties, so nothing is mis-folded. 3.0 admits boolean additionalProperties, and true can only loosen relative to the removed keyword, matching the "loses detail, never meaning" contract.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

… true in 3.0

The previous commit only kept an existing `additionalProperties: true`.
Every other object with `patternProperties` still came out of 3.0 as a
bare `{ type: object }`, which openapi-typescript types as
`Record<string, never>`: Zod `z.looseRecord` (no `additionalProperties`),
Effect records with `onExcessProperty: "error"` (`false`), an
`additionalProperties` schema, and `properties` beside `patternProperties`.
`finishSchema` now sets `additionalProperties: true` whenever
`patternProperties` is present, and the field converter drops the input
one as on main.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FZoWKNpDyGBHkdaur4kKtA
@dinwwwh dinwwwh changed the title fix(downgrader): keep additionalProperties: true beside patternProperties in 3.1 to 3.0 fix(downgrader): give patternProperties schemas additionalProperties: true in 3.0 Oct 24, 2026

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ The code change is sound — it can only loosen — but the README now under-describes it and a Limitations bullet contradicts the new behavior. One minor wording fix suggested inline.

Reviewed changes — re-reviewed the delta since the prior Pullfrog review (518de8b), which had kept additionalProperties: true in the field converter.

  • Fix moved into finishSchema (packages/downgrader/src/v3.1-to-v3.0.ts:654): the field converter is back to main's behavior (always drops the input additionalProperties beside patternProperties), and finishSchema now sets additionalProperties: true whenever schema.patternProperties !== undefined — so undefined, true, false, and schema inputs all end up as true.
  • README row reworded (packages/downgrader/README.md:193): the patternProperties row now says the additionalProperties becomes true.
  • Test rewritten (packages/downgrader/tests/v3.1-to-v3.0.test.ts:1103-1114): loops the four additionalProperties inputs and adds a properties-beside-patternProperties case.

I confirmed the assertion is live (removing the finishSchema block fails the test with expected { type: 'object' } to deeply equal { type: 'object', additionalProperties: true }) and the full 90-test file passes. The change is safe by construction: dropping patternProperties and adding additionalProperties: true only widens acceptance, and patternProperties stays in RESTRICTING_KEYWORDS so the schema is still marked loose.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Comment thread packages/downgrader/README.md Outdated
…ties: true

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FZoWKNpDyGBHkdaur4kKtA
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants