Skip to content

feat(useSearchParams): Add Date and Zod codec support#368

Merged
huntabyte merged 6 commits intosvecosystem:mainfrom
techniq:use-search-params-date-support
Nov 7, 2025
Merged

feat(useSearchParams): Add Date and Zod codec support#368
huntabyte merged 6 commits intosvecosystem:mainfrom
techniq:use-search-params-date-support

Conversation

@techniq
Copy link
Contributor

@techniq techniq commented Oct 21, 2025

Enhances useSearchParams with Date handling (standard schema compatible) and advanced Zod codec support for custom serialization.

🎯 Key Features

1. Native Date Support

  • Automatic Date parsing: URL date strings are automatically converted to Date objects
  • Two date formats:
    • 'date': YYYY-MM-DD format (e.g., 2025-10-21) - perfect for calendar dates
    • 'datetime': Full ISO8601 format (e.g., 2025-10-21T18:18:14.196Z) - preserves exact timestamps
  • Flexible configuration: Set formats via schema property or dateFormats option
  • Works with createSearchParamsSchema, Zod, Valibot, and Arktype

2. Zod Codec Support (v4.1.0+)

  • Custom bidirectional transformations: Define custom encode/decode logic for any type
  • Advanced use cases:
    • Unix timestamps (compact date storage)
    • Base36 encoded IDs (shorter URLs)
    • Custom date formats
    • Legacy API compatibility
  • Automatic codec detection: Extracts encoders from Zod schemas (including .default() wrapped codecs)
  • Server-side support: Works seamlessly with validateSearchParams

📝 Examples

Date Format Support

const schema = createSearchParamsSchema({
  birthDate: { type: 'date', default: new Date('1990-01-15'), dateFormat: 'date' },
  createdAt: { type: 'date', default: new Date(), dateFormat: 'datetime' }
});
// URL: ?birthDate=1990-01-15&createdAt=2025-10-21T18:18:14.196Z

Zod Codecs

const unixTimestamp = z.codec(z.coerce.number(), z.date(), {
  decode: (ts) => new Date(ts * 1000),
  encode: (date) => Math.floor(date.getTime() / 1000)
});

const schema = z.object({
  createdAfter: unixTimestamp.default(new Date('2024-01-01'))
});
// URL: ?createdAfter=1704067200 (instead of 2024-01-01T00:00:00.000Z)

🔧 Technical Changes

  • Added dateFields, dateFormats, codecEncoders, and codecFields tracking to schema info
  • Implemented extractZodCodecEncoder() for codec detection (handles direct and .default() wrapped)
  • Enhanced serializeValue() to respect date formats and codec encoders
  • Updated extractParamValues() to automatically convert date strings to Date objects
  • Modified value setting logic to encode OUTPUT types to INPUT types before validation
  • Extended validateSearchParams with dateFormats option for server-side consistency

✅ Testing

  • 30 unit tests for date parsing, validation, and serialization
  • 7 integration tests for Zod codec scenarios (date-only, datetime, Unix timestamps)
  • All existing tests remain passing (no breaking changes)

📚 Documentation

Comprehensive documentation added covering:

  • Date format configuration (schema property vs options)
  • When to use dateFormats vs Zod codecs
  • Real-world codec examples (Unix timestamps, date-only, base36 IDs)
  • Server-side usage patterns
  • Comparison table: dateFormats vs codecs

@changeset-bot
Copy link

changeset-bot bot commented Oct 21, 2025

🦋 Changeset detected

Latest commit: 3416838

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
runed Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cofl
Copy link

cofl commented Oct 29, 2025

(not review, just commenting) Would it be useful to also/instead support devalue? That way users could use the same transport hook as they do for load and remote functions (aside: is there a way we can use that hook automatically? might be a good feature request for SvelteKit.), and support any arbitrary type rather than explicitly Dates and whatever Zod codecs they've written. This would also be useful for Valibot and ArkType users, since they don't have access to Zod codecs.

@techniq
Copy link
Contributor Author

techniq commented Oct 31, 2025

Hey @cofl 👋 - devalue might be helpful to replace the current usage of JSON.stringify / JSON.parse to support cyclical references, Map/Set, etc, but the use case of adding codec support (which is optional) is to have more control over the encode/decode process.

For example, with a Date instance, you might want to encode it as:

  • full ISO8601 string (YYYY-MM-DDTHH:MM:SSZ)
  • date-only ISO8601 string (YYYY-MM-DD)
  • epoch seconds (date.getTime() / 1000)
  • epoch milliseconds (date.getTime())

Codecs provide the flexibility to control both directions. This also means adding support for other data types, such as Uint8Array or BigInt, is simply to create the codec and then using it within your schema.

const stringToBigInt = z.codec(z.string(), z.bigint(), {
  decode: (str) => BigInt(str),
  encode: (bigint) => bigint.toString(),
});
const utf8ToBytes = z.codec(z.string(), z.instanceof(Uint8Array), {
  decode: (str) => new TextEncoder().encode(str),
  encode: (bytes) => new TextDecoder().decode(bytes),
});
const zodSchema = z.object({
  num: stringToBigInt.default(0n),
  data: utf8ToBytes,
});

Valibot is also investigating shipping something similar to codecs in the future.

With all that said, codecs are purely optional, including for full and date-only ISO8601 strings (but is needed to support epoch time). This PR also supports Date directly with createSearchParamsSchema for both ISO8601 use cases as well as with standard schema via default (only for full ISO8601, but you can be explicit when you validate to use date-only)

  • Full ISO8601 string
const schema = createSearchParamsSchema({
  startDate: { type: "date", default: new Date() }
});
  • Date-only ISO8601 string
const schema = createSearchParamsSchema({
  startDate: { type: "date", default: new Date(), dateFormat: 'date' }
});

@huntabyte
Copy link
Member

Hey @techniq completely missed this PR! Will check it out now!

@github-actions
Copy link
Contributor

github-actions bot commented Nov 6, 2025

built with Refined Cloudflare Pages Action

⚡ Cloudflare Pages Deployment

Name Status Preview Last Commit
runed ❌ Failed (View Log) 3416838

@github-actions github-actions bot requested a deployment to Preview November 6, 2025 20:26 Abandoned
@huntabyte
Copy link
Member

Also I'm handling those merge conflicts I just created so don't worry about em :)

@huntabyte
Copy link
Member

Currently having a test around compression fail with the merge so debugging that and will get this released once done.

@github-actions github-actions bot requested a deployment to Preview November 6, 2025 20:41 Abandoned
@github-actions github-actions bot requested a deployment to Preview November 6, 2025 20:50 Abandoned
@huntabyte
Copy link
Member

I'm having a single one of your tests fail after merging in main (main includes some changes to how values including commas are handled, previously we naively assumed if it included a comma it was an array, which is not always the case). Unsure if that would have an impact on that test or not though? @techniq https://github.com/svecosystem/runed/actions/runs/19149411425/job/54735468957?pr=368#step:7:156

Copy link
Member

@huntabyte huntabyte left a comment

Choose a reason for hiding this comment

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

Awesome job!

@github-actions github-actions bot requested a deployment to Preview November 7, 2025 00:10 Abandoned
@huntabyte huntabyte merged commit 3af0066 into svecosystem:main Nov 7, 2025
5 checks passed
@techniq
Copy link
Contributor Author

techniq commented Nov 7, 2025

Thanks for the review, fix and release @huntabyte!

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.

3 participants