Skip to content

feat: allow customizing schema combination#313

Merged
juliusmarminge merged 17 commits into
t3-oss:mainfrom
EskiMojo14:object-schema
Apr 23, 2025
Merged

feat: allow customizing schema combination#313
juliusmarminge merged 17 commits into
t3-oss:mainfrom
EskiMojo14:object-schema

Conversation

@EskiMojo14

Copy link
Copy Markdown
Contributor

This allows extension like refinement and transformation.

const env = createEnv({
  server,
  client,
  shared,
  runtimeEnv: process.env,
  createFinalSchema: (shape) => z.object(shape).refine(extraCheck),
  // or
  createFinalSchema: (shape) => pipe(object(shape), check(extraCheck))
})

fixes #268
alternative approach to #169

@vercel

vercel Bot commented Jan 27, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
t3-env ✅ Ready (Inspect) Visit Preview 💬 Add feedback Apr 23, 2025 9:23pm

@vercel

vercel Bot commented Jan 27, 2025

Copy link
Copy Markdown

@EskiMojo14 is attempting to deploy a commit to the t3-oss Team on Vercel.

A member of the Team first needs to authorize it.

Comment thread docs/src/app/docs/customization/page.mdx
Comment thread packages/core/src/index.ts Outdated
Comment thread packages/core/src/index.ts Outdated
Co-authored-by: Julius Marminge <jmarminge@gmail.com>
Comment on lines +13 to +14
- `CreateEnv` now has the signature `CreateEnv<TFinalSchema, TExtends>`, instead of the previous `CreateEnv<TServer, TClient, TShared, TExtends>`.
- Previous behaviour can be achieved by using `DefaultCombinedSchema<TServer, TClient, TShared>` as the type for `TFinalSchema`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is this a user-breaking change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

CreateEnv is exported, so technically yes - in practicality I doubt anyone was relying on it

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's exported cause the other packages needs it. I should add some @internal jsdoc annotations to things

Comment thread packages/core/test/smoke-valibot.test.ts
});
});

describe("createFinalSchema", () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

how does this work with extends? can you add some tests for that

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

can do - extends just assigns all of the properties to the final result from the schema, like it did before

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

what I was thinking of was more like what if you do some combinator that accesses server stuff, will that cause an "invalid server access" or not? I'd think not since you're not giving the proxy as the argument there right? also what are the types in that callback like, server variables will be undefined when the combinator runs on the client etc etc

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

see #240, the whole Proxy thing doesn't really work properly with extends - but that's irrelevant to createFinalSchema, because the proxy isn't applied until the schema is finished validating.

As for types, you're correct - it's currently typed to what it'd be on the server, like the rest of the library (client + server + shared). I don't know how much it would complicate things to mark the server variables as optional.

@EskiMojo14 EskiMojo14 Jan 31, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

had a brainwave and opened #316 to fix #240

coming back to this - schemas have always been allowed to access any key they want, though that'll only include the ones that have already been checked with the current schema (not any extended ones), since the schema ditches unknown keys.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

schemas have always been allowed to access any key they want

No since accessing a server var on the client would throw an error - so e.g if you'd chain on a toLower() you'd get the invalid access before. It seems here we'd get a "Cannot access property toLower of undefined" which feels suboptimal - but maybe I'm overthinking it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

schemas have never been run on the output of presets, they were always run before it. they have always been passed the raw runtimeEnv option, and any presets were merged into the parsed result.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I understand the concern, but I don't think there's a good way to expose type-wise that the server variables might not be defined to the createFinalSchema callback. Best case, the schema result also reflects the server variables as optional (whereas they're currently always defined), worst case the inference breaks completely. We also can't wrap the intermediate parsed value in a Proxy since we don't have access to it.

We could possibly pass some sort of makeSafe function to the user that would make a Proxy, but that would need to be opt in.

createObjectSchema: (shape, { isServer, makeSafe }) => z.object(shape).refine((_env) => {
  const env = makeSafe(_env) // wraps in Proxy
  if (!isServer) env.SERVER_ENV // throws
  // ...
})

Comment thread packages/core/test/smoke-zod.test.ts
@juliusmarminge juliusmarminge changed the title Expose an option to customise schema combination feat: allow customizing schema combination Apr 23, 2025
@juliusmarminge juliusmarminge added this pull request to the merge queue Apr 23, 2025
Merged via the queue into t3-oss:main with commit 35577fc Apr 23, 2025
@EskiMojo14 EskiMojo14 deleted the object-schema branch April 23, 2025 21:39
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.

Support for conditionally requiring fields/envs?

2 participants