Skip to content

Commit

Permalink
feat: directly pass zod objects to server and client
Browse files Browse the repository at this point in the history
  • Loading branch information
feder240516 committed Oct 8, 2024
1 parent e7e2109 commit 91935e2
Show file tree
Hide file tree
Showing 5 changed files with 336 additions and 198 deletions.
161 changes: 107 additions & 54 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import type { TypeOf, ZodError, ZodObject, ZodType } from "zod";
import type {
TypeOf,
UnknownKeysParam,
ZodError,
ZodObject,
ZodRawShape,
ZodTypeAny,
} from "zod";
import { object } from "zod";

export type ErrorMessage<T extends string> = T;
Expand All @@ -25,7 +32,7 @@ type Reduce<
: never;

export interface BaseOptions<
TShared extends Record<string, ZodType>,
TShared extends SchemaObject,
TExtends extends Array<Record<string, unknown>>,
> {
/**
Expand Down Expand Up @@ -80,7 +87,7 @@ export interface BaseOptions<
}

export interface LooseOptions<
TShared extends Record<string, ZodType>,
TShared extends SchemaObject,
TExtends extends Array<Record<string, unknown>>,
> extends BaseOptions<TShared, TExtends> {
runtimeEnvStrict?: never;
Expand All @@ -95,9 +102,9 @@ export interface LooseOptions<

export interface StrictOptions<
TPrefix extends string | undefined,
TServer extends Record<string, ZodType>,
TClient extends Record<string, ZodType>,
TShared extends Record<string, ZodType>,
TServer extends SchemaObject,
TClient extends SchemaObject,
TShared extends SchemaObject,
TExtends extends Array<Record<string, unknown>>,
> extends BaseOptions<TShared, TExtends> {
/**
Expand All @@ -106,30 +113,32 @@ export interface StrictOptions<
*/
runtimeEnvStrict: Record<
| {
[TKey in keyof TClient]: TPrefix extends undefined
[TKey in keyof SchemaShape<TClient>]: TPrefix extends undefined
? never
: TKey extends `${TPrefix}${string}`
? TKey
: never;
}[keyof TClient]
}[keyof SchemaShape<TClient>]
| {
[TKey in keyof TServer]: TPrefix extends undefined
[TKey in keyof SchemaShape<TServer>]: TPrefix extends undefined
? TKey
: TKey extends `${TPrefix}${string}`
? never
: TKey;
}[keyof TServer]
}[keyof SchemaShape<TServer>]
| {
[TKey in keyof TShared]: TKey extends string ? TKey : never;
}[keyof TShared],
[TKey in keyof SchemaShape<TShared>]: TKey extends string
? TKey
: never;
}[keyof SchemaShape<TShared>],
string | boolean | number | undefined
>;
runtimeEnv?: never;
}

export interface ClientOptions<
TPrefix extends string | undefined,
TClient extends Record<string, ZodType>,
TClientSchema extends SchemaObject,
> {
/**
* The prefix that client-side variables must have. This is enforced both at
Expand All @@ -141,61 +150,105 @@ export interface ClientOptions<
* Specify your client-side environment variables schema here. This way you can ensure the app isn't
* built with invalid env vars.
*/
client: Partial<{
[TKey in keyof TClient]: TKey extends `${TPrefix}${string}`
? TClient[TKey]
: ErrorMessage<`${TKey extends string
? TKey
: never} is not prefixed with ${TPrefix}.`>;
}>;
client: TClientSchema extends ZodObject<
infer R1,
infer R2,
infer R3,
infer R4,
unknown
>
? TClientSchema extends never
? TClientSchema
: ZodObject<
R1,
R2,
R3,
R4,
{
[TKey in keyof TClientSchema["_input"]]: TKey extends `${TPrefix}${string}`
? TClientSchema[TKey]
: ErrorMessage<`${TKey extends string
? TKey
: never} is not prefixed with ${TPrefix}.`>;
}
>
: never;
}

type SchemaShape<T extends SchemaObject> = T["_input"]; // extends Schema<any, any, infer Shape> ? Shape : never

export type SchemaObject = ZodObject<
ZodRawShape,
UnknownKeysParam,
ZodTypeAny,
{},
{}
>;

export interface ServerOptions<
TPrefix extends string | undefined,
TServer extends Record<string, ZodType>,
TServerSchema extends SchemaObject,
> {
/**
* Specify your server-side environment variables schema here. This way you can ensure the app isn't
* built with invalid env vars.
*/
server: Partial<{
[TKey in keyof TServer]: TPrefix extends undefined
? TServer[TKey]
: TPrefix extends ""
? TServer[TKey]
: TKey extends `${TPrefix}${string}`
? ErrorMessage<`${TKey extends `${TPrefix}${string}`
? TKey
: never} should not prefixed with ${TPrefix}.`>
: TServer[TKey];
}>;
server: TServerSchema extends ZodObject<
infer R1,
infer R2,
infer R3,
infer R4,
unknown
>
? TServerSchema extends never
? TServerSchema
: TPrefix extends undefined
? TServerSchema
: TPrefix extends ""
? TServerSchema
: ZodObject<
R1,
R2,
R3,
R4,
{
[TKey in keyof TServerSchema["_input"]]: TKey extends `${TPrefix}${string}`
? ErrorMessage<`${TKey extends `${TPrefix}${string}`
? TKey
: never} should not prefixed with ${TPrefix}.`>
: TServerSchema["_input"][TKey];
}
>
: never;
}

export type ServerClientOptions<
TPrefix extends string | undefined,
TServer extends Record<string, ZodType>,
TClient extends Record<string, ZodType>,
TServer extends SchemaObject,
TClient extends SchemaObject,
> =
| (ClientOptions<TPrefix, TClient> & ServerOptions<TPrefix, TServer>)
| (ServerOptions<TPrefix, TServer> & Impossible<ClientOptions<never, never>>)
| (ClientOptions<TPrefix, TClient> & Impossible<ServerOptions<never, never>>);

export type EnvOptions<
TPrefix extends string | undefined,
TServer extends Record<string, ZodType>,
TClient extends Record<string, ZodType>,
TShared extends Record<string, ZodType>,
TServer extends SchemaObject,
TClient extends SchemaObject,
TShared extends SchemaObject,
TExtends extends Array<Record<string, unknown>>,
> =
| (LooseOptions<TShared, TExtends> &
ServerClientOptions<TPrefix, TServer, TClient>)
| (StrictOptions<TPrefix, TServer, TClient, TShared, TExtends> &
ServerClientOptions<TPrefix, TServer, TClient>);
| ((StrictOptions<TPrefix, TServer, TClient, TShared, TExtends> &
ServerClientOptions<TPrefix, TServer, TClient>) & {
a?: ServerClientOptions<TPrefix, TServer, TClient>["client"];
});

type TPrefixFormat = string | undefined;
type TServerFormat = Record<string, ZodType>;
type TClientFormat = Record<string, ZodType>;
type TSharedFormat = Record<string, ZodType>;
type TServerFormat = SchemaObject;
type TClientFormat = SchemaObject;
type TSharedFormat = SchemaObject;
type TExtendsFormat = Array<Record<string, unknown>>;

export type CreateEnv<
Expand All @@ -205,18 +258,18 @@ export type CreateEnv<
TExtends extends TExtendsFormat,
> = Readonly<
Simplify<
TypeOf<ZodObject<TServer>> &
TypeOf<ZodObject<TClient>> &
TypeOf<ZodObject<TShared>> &
TypeOf<TServer> &
TypeOf<TClient> &
TypeOf<TShared> &
UnReadonlyObject<Reduce<TExtends>>
>
>;

export function createEnv<
TPrefix extends TPrefixFormat,
TServer extends TServerFormat = NonNullable<unknown>,
TClient extends TClientFormat = NonNullable<unknown>,
TShared extends TSharedFormat = NonNullable<unknown>,
TServer extends TServerFormat = SchemaObject,
TClient extends TClientFormat = SchemaObject,
TShared extends TSharedFormat = SchemaObject,
const TExtends extends TExtendsFormat = [],
>(
opts: EnvOptions<TPrefix, TServer, TClient, TShared, TExtends>,
Expand All @@ -236,12 +289,12 @@ export function createEnv<
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
if (skip) return runtimeEnv as any;

const _client = typeof opts.client === "object" ? opts.client : {};
const _server = typeof opts.server === "object" ? opts.server : {};
const _shared = typeof opts.shared === "object" ? opts.shared : {};
const client = object(_client);
const server = object(_server);
const shared = object(_shared);
const _client = typeof opts.client === "object" ? opts.client : object({});
const _server = typeof opts.server === "object" ? opts.server : object({});
const _shared = typeof opts.shared === "object" ? opts.shared : object({});
const client = _client;
const server = _server;
const shared = _shared;
const isServer =
opts.isServer ?? (typeof window === "undefined" || "Deno" in window);

Expand Down
20 changes: 10 additions & 10 deletions packages/core/src/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { createEnv } from ".";
*/
export const vercel = () =>
createEnv({
server: {
server: z.object({
VERCEL: z.string().optional(),
VERCEL_ENV: z.enum(["development", "preview", "production"]).optional(),
VERCEL_URL: z.string().optional(),
Expand All @@ -26,7 +26,7 @@ export const vercel = () =>
VERCEL_GIT_COMMIT_AUTHOR_NAME: z.string().optional(),
VERCEL_GIT_PREVIOUS_SHA: z.string().optional(),
VERCEL_GIT_PULL_REQUEST_ID: z.string().optional(),
},
}),
runtimeEnv: process.env,
});

Expand All @@ -35,10 +35,10 @@ export const vercel = () =>
*/
export const uploadthing = () =>
createEnv({
server: {
server: z.object({
UPLOADTHING_SECRET: z.string(),
UPLOADTHING_APP_ID: z.string().optional(),
},
}),
runtimeEnv: process.env,
});

Expand All @@ -48,7 +48,7 @@ export const uploadthing = () =>
*/
export const render = () =>
createEnv({
server: {
server: z.object({
IS_PULL_REQUEST: z.string().optional(),
RENDER_DISCOVERY_SERVICE: z.string().optional(),
RENDER_EXTERNAL_HOSTNAME: z.string().optional(),
Expand All @@ -63,7 +63,7 @@ export const render = () =>
.enum(["web", "pserv", "cron", "worker", "static"])
.optional(),
RENDER: z.string().optional(),
},
}),
runtimeEnv: process.env,
});

Expand All @@ -73,7 +73,7 @@ export const render = () =>
*/
export const railway = () =>
createEnv({
server: {
server: z.object({
RAILWAY_PUBLIC_DOMAIN: z.string().optional(),
RAILWAY_PRIVATE_DOMAIN: z.string().optional(),
RAILWAY_TCP_PROXY_DOMAIN: z.string().optional(),
Expand All @@ -97,7 +97,7 @@ export const railway = () =>
RAILWAY_GIT_REPO_NAME: z.string().optional(),
RAILWAY_GIT_REPO_OWNER: z.string().optional(),
RAILWAY_GIT_COMMIT_MESSAGE: z.string().optional(),
},
}),
runtimeEnv: process.env,
});

Expand All @@ -107,7 +107,7 @@ export const railway = () =>
*/
export const fly = () =>
createEnv({
server: {
server: z.object({
FLY_APP_NAME: z.string().optional(),
FLY_MACHINE_ID: z.string().optional(),
FLY_ALLOC_ID: z.string().optional(),
Expand All @@ -119,6 +119,6 @@ export const fly = () =>
FLY_PROCESS_GROUP: z.string().optional(),
FLY_VM_MEMORY_MB: z.string().optional(),
PRIMARY_REGION: z.string().optional(),
},
}),
runtimeEnv: process.env,
});
Loading

0 comments on commit 91935e2

Please sign in to comment.