fix: type single relation/select/file fields as single, not arrays - #35
Conversation
PocketBase stores single-value relation, select, and file fields with maxSelect: 0 (not 1). The generators only treated maxSelect === 1 as single, so those fields were emitted as arrays (string[], (...)[], z.array(...)). Follow PocketBase's own rule — multiple iff maxSelect > 1 — via a shared isMultipleField helper used by the type generator, relation extraction, and the Zod plugin. Add regression tests for maxSelect: 0. Closes #28 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
This PR correctly fixes the maxSelect: 0 → single-value typing bug across the type generator, relation extraction, and Zod plugin, and the shared isMultipleField helper is a clean abstraction. One test-coverage gap noted below.
- Missing Zod regression test: the type generator has explicit
maxSelect: 0tests, but the Zod plugin does not — the fixture only usesmaxSelect: 1for single fields, so a future regression infieldToZodwould go undetected.
| break; | ||
| case "file": | ||
| schema = field.options.maxSelect === 1 ? "z.string()" : "z.array(z.string())"; | ||
| schema = isMultipleField(field) ? "z.array(z.string())" : "z.string()"; |
There was a problem hiding this comment.
Missing regression test for maxSelect: 0 in the Zod plugin.
The type generator gains three explicit maxSelect: 0 tests in type-generator.test.ts, but the Zod plugin's zod.test.ts has no equivalent. The test fixture (full-schema.json) uses maxSelect: 1 for every single-value relation/select/file field, so the snapshot and assertion tests never exercise the maxSelect: 0 path that this PR fixes.
Impact: if someone later replaces isMultipleField(field) with a direct maxSelect === 1 check (re-introducing the original bug), CI will not catch it in the Zod output.
Fix: either add a maxSelect: 0 field to packages/pbkit-zod/src/test/fixtures/full-schema.json (and update the snapshot), or add a targeted unit test similar to the type generator's:
test("relation maxSelect=0 → z.string() (not array)", () => {
const schema = parseJson({ collections: [{ name: "x", type: "base", fields: [{ name: "ref", type: "relation", system: false, maxSelect: 0, collectionId: "abc" }] }] });
const output = generateZod(schema, { ir: schema, typesImport: "./t", sdkImport: "./s" });
expect(output).toContain("ref: z.string()");
});There was a problem hiding this comment.
Good catch — added in c050a63. New regression test in zod.test.ts builds a collection with maxSelect: 0 relation/select/file fields and asserts they generate z.string() / z.enum([...]) (not z.array(...)), so a future regression in fieldToZod would now fail CI.
The Zod fixture only used maxSelect: 1, so the maxSelect: 0 path fixed in this PR was unguarded. Add a targeted test asserting single relation/select/file fields generate z.string()/z.enum(), not z.array(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
No new major issues found. The |
Closes #28.
Problem
PocketBase stores single-value relation, select, and file fields with
maxSelect: 0— not1. The generators only treatedmaxSelect === 1as single, so every single relation/select/file field was emitted as an array:Thanks to @-the reporter for the precise diagnosis in the issue.
Fix
Follow PocketBase's own semantics — a field is multiple only when
maxSelect > 1(matching core'sIsMultiple()), so0and1are both single. Centralized in a sharedisMultipleFieldhelper to stop the rule from drifting across call sites:type-generator/generate.ts—select,relation,fileschema-parser/normalize.ts—extractRelationsmultipleflag (also affected expand-path arity)pbkit-zod—select,relation,fileschemasTests
maxSelect: 0(relation, select, file) in the type generatorbun run ci(lint, typecheck, test, build) — 13/13 turbo tasks passRelease
Patch changeset added for
@karnak19/pbkitand@karnak19/pbkit-zod. The TanStack plugin is unaffected (it derives from generated types, notmaxSelect).🤖 Generated with Claude Code