feat(orm): add field-level @fuzzy attribute to gate fuzzy search#2642
feat(orm): add field-level @fuzzy attribute to gate fuzzy search#2642
Conversation
Adds a new ZModel field-level `@fuzzy` attribute that opts a `String` field into fuzzy search. Without `@fuzzy`, the `fuzzy` filter operator and `_fuzzyRelevance` orderBy are unavailable both at the type level and at runtime. - Language: declare `@fuzzy` in stdlib (StringField only, @@@once) - Validator: enforce postgres-only via shared `getDataSourceProvider` helper - Schema: surface `fuzzy?: boolean` on `FieldDef`; generator emits it - ORM types: gate `fuzzy:` filter and `_fuzzyRelevance.fields` to opted-in fields - Zod factory: thread `withFuzzy` through string filter; filter `_fuzzyRelevance` enum - Runtime: throw `InvalidInputError` when fuzzy is used on non-`@fuzzy` fields - Tests: dedicated `fuzzy-search` test schema; validator coverage for postgres/sqlite/mysql/non-String Breaking: existing schemas using fuzzy search must annotate fields with `@fuzzy` to retain access. Fuzzy search shipped only one PR ago (#2573). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds a field-level ChangesFuzzy-Search Attribute and Integration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Claude finished @ymc9's task —— View job PR Review: Field-level @fuzzy attribute implementationI've conducted a comprehensive review of this PR that introduces the ✅ Strengths📚 Language Layer Excellence
**🏗️ Schema & Generation Robustness
**🎯 Type System Precision
**⚡ Runtime Validation
**🔧 Zod Integration
🧪 Comprehensive TestingPositive tests: Dedicated 🚀 Architecture & DesignBreaking change handling: Well-documented and justified - minimal real-world impact given fuzzy search was just introduced
|
There was a problem hiding this comment.
🧹 Nitpick comments (3)
packages/language/res/stdlib.zmodel (1)
408-408: 💤 Low valueOptional: consider adding
@@@onceto prevent duplicate@fuzzyannotations.Without
@@@once, a field with@fuzzy@fuzzy`` would silently produce duplicate entries in the generatedattributesarray (though `fuzzy: true` is idempotent). Adding `@@@once` makes the constraint explicit and consistent with other single-application attributes like `@id` and `@unique`.✨ Proposed fix
-attribute `@fuzzy`() @@@targetField([StringField]) +attribute `@fuzzy`() @@@targetField([StringField]) @@@once🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/language/res/stdlib.zmodel` at line 408, The attribute declaration "attribute `@fuzzy`() @@@targetField([StringField])" can be applied multiple times and may produce duplicate entries; update the attribute definition to include the singleton constraint by adding "@@@once" to the attribute metadata (i.e., change the attribute declaration for `@fuzzy` to "attribute `@fuzzy`() @@@once @@@targetField([StringField])") so the model enforces single application like other single-use attributes such as `@id` and `@unique`.packages/orm/src/client/crud/dialects/base-dialect.ts (1)
1132-1139: 💤 Low value
requireFieldis called twice per field — once for validation, once insidebuildFieldRef.The validation loop (line 1133) calls
requireField, then line 1139'sbuildFieldRefcall invokes it again. For a smallvalue.fieldsarray this is negligible, but you can avoid the double lookup by storing the refs during validation:♻️ Proposed refactor
- for (const fieldName of value.fields as string[]) { - const fieldDef = requireField(this.schema, model, fieldName); - invariant( - fieldDef.fuzzy === true, - `field "${fieldName}" is not fuzzy-searchable; add the \`@fuzzy\` attribute to use it in \`_fuzzyRelevance\``, - ); - } - const fieldRefs = value.fields.map((f: string) => buildFieldRef(model, f, modelAlias)); + const fieldRefs = (value.fields as string[]).map((fieldName) => { + const fieldDef = requireField(this.schema, model, fieldName); + invariant( + fieldDef.fuzzy === true, + `field "${fieldName}" is not fuzzy-searchable; add the \`@fuzzy\` attribute to use it in \`_fuzzyRelevance\``, + ); + return buildFieldRef(model, fieldName, modelAlias); + });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/orm/src/client/crud/dialects/base-dialect.ts` around lines 1132 - 1139, The code calls requireField twice for each entry in value.fields (once in the validation loop and again inside buildFieldRef), causing redundant lookups; modify the loop that iterates value.fields to both validate and build/cache the field reference in one pass (use requireField to get fieldDef and then call buildFieldRef once per field), collect those refs into fieldRefs and remove the separate map call so buildFieldRef is not invoked a second time; update any use of modelAlias, model, schema, requireField, buildFieldRef, and fieldRefs accordingly.tests/e2e/orm/client-api/fuzzy-search.test.ts (1)
820-822: 💤 Low valueSection label "E" is a duplicate — should be "R".
"E. fuzzy in logical compositions" already appears earlier in the file (line ~204). This new block should be "R" (the next letter after Q at line 786) to keep the section labels sequential.
🔤 Suggested fix
- // E. `@fuzzy` attribute gating + // R. `@fuzzy` attribute gating🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/e2e/orm/client-api/fuzzy-search.test.ts` around lines 820 - 822, Change the duplicated section label "E. `@fuzzy` attribute gating" to "R. `@fuzzy` attribute gating" so section letters remain sequential (the earlier "E. fuzzy in logical compositions" exists already); locate the header string "E. `@fuzzy` attribute gating" in tests/e2e/orm/client-api/fuzzy-search.test.ts and replace the leading letter only, ensuring the rest of the heading text and any related comments or test descriptions (e.g., the surrounding section divider lines) remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@packages/language/res/stdlib.zmodel`:
- Line 408: The attribute declaration "attribute `@fuzzy`()
@@@targetField([StringField])" can be applied multiple times and may produce
duplicate entries; update the attribute definition to include the singleton
constraint by adding "@@@once" to the attribute metadata (i.e., change the
attribute declaration for `@fuzzy` to "attribute `@fuzzy`() @@@once
@@@targetField([StringField])") so the model enforces single application like
other single-use attributes such as `@id` and `@unique`.
In `@packages/orm/src/client/crud/dialects/base-dialect.ts`:
- Around line 1132-1139: The code calls requireField twice for each entry in
value.fields (once in the validation loop and again inside buildFieldRef),
causing redundant lookups; modify the loop that iterates value.fields to both
validate and build/cache the field reference in one pass (use requireField to
get fieldDef and then call buildFieldRef once per field), collect those refs
into fieldRefs and remove the separate map call so buildFieldRef is not invoked
a second time; update any use of modelAlias, model, schema, requireField,
buildFieldRef, and fieldRefs accordingly.
In `@tests/e2e/orm/client-api/fuzzy-search.test.ts`:
- Around line 820-822: Change the duplicated section label "E. `@fuzzy` attribute
gating" to "R. `@fuzzy` attribute gating" so section letters remain sequential
(the earlier "E. fuzzy in logical compositions" exists already); locate the
header string "E. `@fuzzy` attribute gating" in
tests/e2e/orm/client-api/fuzzy-search.test.ts and replace the leading letter
only, ensuring the rest of the heading text and any related comments or test
descriptions (e.g., the surrounding section divider lines) remain unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 917aab72-a24c-49ba-a1a5-179ebffcf932
📒 Files selected for processing (18)
packages/language/res/stdlib.zmodelpackages/language/src/document.tspackages/language/src/utils.tspackages/language/src/validators/attribute-application-validator.tspackages/language/test/attribute-application.test.tspackages/orm/src/client/crud-types.tspackages/orm/src/client/crud/dialects/base-dialect.tspackages/orm/src/client/zod/factory.tspackages/schema/src/schema.tspackages/sdk/src/ts-schema-generator.tstests/e2e/orm/client-api/fuzzy-search.test.tstests/e2e/orm/schemas/basic/input.tstests/e2e/orm/schemas/basic/models.tstests/e2e/orm/schemas/basic/schema.tstests/e2e/orm/schemas/basic/schema.zmodeltests/e2e/orm/schemas/fuzzy-search/index.tstests/e2e/orm/schemas/fuzzy-search/schema.tstests/e2e/orm/schemas/fuzzy-search/schema.zmodel
💤 Files with no reviewable changes (4)
- tests/e2e/orm/schemas/basic/schema.ts
- tests/e2e/orm/schemas/basic/schema.zmodel
- tests/e2e/orm/schemas/basic/input.ts
- tests/e2e/orm/schemas/basic/models.ts
|
Claude finished @ymc9's task —— View job PR Review: Field-level @fuzzy attribute implementationI've conducted a comprehensive review of this PR that introduces the ✅ Strengths📚 Language Layer Excellence
**🏗️ Schema & Generation Robustness
**🎯 Type System Precision
**⚡ Runtime Validation
**🔧 Zod Integration
🧪 Comprehensive TestingPositive tests: Dedicated 🚀 Architecture & DesignBreaking change handling: Well-documented and justified - minimal real-world impact given fuzzy search was just introduced
|
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Claude finished @ymc9's task —— View job Code Review: Field-level @fuzzy attribute implementationI've conducted a comprehensive review of this PR that introduces the ✅ Strengths📚 Language Layer Excellence
🏗️ Schema & Generation Robustness
🎯 Type System Precision
⚡ Runtime Validation
🔧 Zod Integration
🧪 Comprehensive TestingPositive tests: Dedicated 🚀 Architecture & DesignBreaking change handling: Well-documented and justified - minimal real-world impact given fuzzy search was just introduced
|
|
Claude finished @ymc9's task —— View job PR Review: Field-level @fuzzy attribute implementationI've conducted a comprehensive review of this PR that introduces the Todo List:
✅ Strengths📚 Language Layer Excellence
🏗️ Schema & Generation Robustness
🎯 Type System Precision
⚡ Runtime Validation
🔧 Zod Integration
🧪 Comprehensive TestingPositive tests: Dedicated 🚀 Architecture & DesignBreaking change handling: Well-documented and justified - minimal real-world impact given fuzzy search was just introduced
|
Summary
@fuzzyfield-level ZModel attribute that opts aStringfield into fuzzy search.fuzzy:filter operator and_fuzzyRelevanceorderBy at the type, zod, and runtime layers — fields without@fuzzyno longer participate.@fuzzyis only allowed when the datasource provider ispostgresql.getDataSourceProviderfromdocument.tsinto sharedlanguage/utils.tsso both the validator and document loader use the same helper.Layer breakdown
@fuzzy() @@@targetField([StringField]) @@@onceinstdlib.zmodel; new@check('@fuzzy')validator gating to postgres provider.fuzzy?: booleantoFieldDef; generator emitsfuzzy: truewhen the attribute is present.AddFuzzyFilterIfSupportednow requiresGetModelField<...>['fuzzy'] extends true; newFuzzyFields<>used by_fuzzyRelevance.fields.withFuzzyflag threaded throughmakeStringFilterSchema/makePrimitiveFilterSchema;_fuzzyRelevance.fieldsenum filters bydef.fuzzy === true.base-dialect.tsthrowsInvalidInputErroriffuzzy:filter or_fuzzyRelevance.fieldsreferences a non-@fuzzyfield.Tests
tests/e2e/orm/schemas/fuzzy-search/schema with@fuzzyonname/descriptionand a non-fuzzynotesfield.fuzzy-search.test.tsto the new schema; added two negative tests for non-@fuzzyfield rejection.Flavorfrombasicschema (was only used for fuzzy testing).attribute-application.test.tscovering postgres/sqlite/mysql/non-String cases.Breaking change
Existing schemas using fuzzy search must annotate target fields with
@fuzzyto retain access. Since fuzzy search shipped only in #2573 (one PR ago), real-world fallout should be minimal.Test plan
fuzzy-search.test.ts(skipped under sqlite/mysql)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Validation
SDK & Types
Tests
Documentation