fix: use schema-first generic pattern for Zod type compatibility
Refactor `SignInResolverFactoryOptions` and `createSchemaFromZod` to use `TSchema extends ZodType` instead of `ZodSchema<Output, Def, Input>`, avoiding "excessively deep" TypeScript inference errors when multiple Zod copies are resolved in a project. Signed-off-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
---
|
||||
'@backstage/plugin-auth-node': minor
|
||||
---
|
||||
|
||||
**BREAKING**: Refactored `SignInResolverFactoryOptions` to use a schema-first generic pattern, following Zod's [recommended approach](https://zod.dev/library-authors?id=how-to-accept-user-defined-schemas#how-to-accept-user-defined-schemas) for writing generic functions. The type parameters changed from `<TAuthResult, TOptionsOutput, TOptionsInput>` to `<TAuthResult, TSchema extends ZodType>`.
|
||||
|
||||
This fixes "Type instantiation is excessively deep and possibly infinite" errors that occurred when the Zod version in a user's project did not align with the one in Backstage core.
|
||||
|
||||
If you use `createSignInResolverFactory` without explicit type parameters (the typical usage), no changes are needed:
|
||||
|
||||
```ts
|
||||
// This usage is unchanged
|
||||
createSignInResolverFactory({
|
||||
optionsSchema: z.object({ domain: z.string() }).optional(),
|
||||
create(options = {}) {
|
||||
/* ... */
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
If you reference `SignInResolverFactoryOptions` with explicit type parameters, update as follows:
|
||||
|
||||
```diff
|
||||
- SignInResolverFactoryOptions<MyAuthResult, MyOutput, MyInput>
|
||||
+ SignInResolverFactoryOptions<MyAuthResult, typeof mySchema>
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/frontend-plugin-api': patch
|
||||
---
|
||||
|
||||
Refactored the internal `createSchemaFromZod` helper to use a schema-first generic pattern, replacing the `ZodSchema<TOutput, ZodTypeDef, TInput>` constraint with `TSchema extends ZodType`. This avoids "excessively deep" type inference errors when multiple Zod copies are resolved.
|
||||
@@ -15,16 +15,16 @@
|
||||
*/
|
||||
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { z, type ZodSchema, type ZodTypeDef } from 'zod/v3';
|
||||
import { z, type ZodIssue, type ZodType } from 'zod/v3';
|
||||
import zodToJsonSchema from 'zod-to-json-schema';
|
||||
import { PortableSchema } from './types';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function createSchemaFromZod<TOutput, TInput>(
|
||||
schemaCreator: (zImpl: typeof z) => ZodSchema<TOutput, ZodTypeDef, TInput>,
|
||||
): PortableSchema<TOutput, TInput> {
|
||||
export function createSchemaFromZod<TSchema extends ZodType>(
|
||||
schemaCreator: (zImpl: typeof z) => TSchema,
|
||||
): PortableSchema<z.output<TSchema>, z.input<TSchema>> {
|
||||
const schema = schemaCreator(z);
|
||||
return {
|
||||
// TODO: Types allow z.array etc here but it will break stuff
|
||||
@@ -41,7 +41,7 @@ export function createSchemaFromZod<TOutput, TInput>(
|
||||
};
|
||||
}
|
||||
|
||||
function formatIssue(issue: z.ZodIssue): string {
|
||||
function formatIssue(issue: ZodIssue): string {
|
||||
if (issue.code === 'invalid_union') {
|
||||
return formatIssue(issue.unionErrors[0].issues[0]);
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ import { Profile } from 'passport';
|
||||
import { Request as Request_2 } from 'express';
|
||||
import { Response as Response_2 } from 'express';
|
||||
import { Strategy } from 'passport';
|
||||
import type { ZodSchema } from 'zod/v3';
|
||||
import type { ZodTypeDef } from 'zod/v3';
|
||||
import type { z } from 'zod/v3';
|
||||
import type { ZodType } from 'zod/v3';
|
||||
|
||||
// @public (undocumented)
|
||||
export interface AuthOwnershipResolutionExtensionPoint {
|
||||
@@ -227,15 +227,10 @@ export function createProxyAuthRouteHandlers<TResult>(
|
||||
// @public (undocumented)
|
||||
export function createSignInResolverFactory<
|
||||
TAuthResult,
|
||||
TOptionsOutput,
|
||||
TOptionsInput,
|
||||
TSchema extends ZodType = ZodType<unknown>,
|
||||
>(
|
||||
options: SignInResolverFactoryOptions<
|
||||
TAuthResult,
|
||||
TOptionsOutput,
|
||||
TOptionsInput
|
||||
>,
|
||||
): SignInResolverFactory<TAuthResult, TOptionsInput>;
|
||||
options: SignInResolverFactoryOptions<TAuthResult, TSchema>,
|
||||
): SignInResolverFactory<TAuthResult, z.input<TSchema>>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function decodeOAuthState(encodedState: string): OAuthState;
|
||||
@@ -676,13 +671,12 @@ export interface SignInResolverFactory<TAuthResult = any, TOptions = any> {
|
||||
// @public (undocumented)
|
||||
export interface SignInResolverFactoryOptions<
|
||||
TAuthResult,
|
||||
TOptionsOutput,
|
||||
TOptionsInput,
|
||||
TSchema extends ZodType = ZodType<unknown>,
|
||||
> {
|
||||
// (undocumented)
|
||||
create(options: TOptionsOutput): SignInResolver<TAuthResult>;
|
||||
create(options: z.output<TSchema>): SignInResolver<TAuthResult>;
|
||||
// (undocumented)
|
||||
optionsSchema?: ZodSchema<TOptionsOutput, ZodTypeDef, TOptionsInput>;
|
||||
optionsSchema?: TSchema;
|
||||
}
|
||||
|
||||
// @public
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ZodSchema, ZodTypeDef } from 'zod/v3';
|
||||
import type { z, ZodType } from 'zod/v3';
|
||||
import { SignInResolver } from '../types';
|
||||
import zodToJsonSchema from 'zod-to-json-schema';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
@@ -34,38 +34,32 @@ export interface SignInResolverFactory<TAuthResult = any, TOptions = any> {
|
||||
/** @public */
|
||||
export interface SignInResolverFactoryOptions<
|
||||
TAuthResult,
|
||||
TOptionsOutput,
|
||||
TOptionsInput,
|
||||
TSchema extends ZodType = ZodType<unknown>,
|
||||
> {
|
||||
optionsSchema?: ZodSchema<TOptionsOutput, ZodTypeDef, TOptionsInput>;
|
||||
create(options: TOptionsOutput): SignInResolver<TAuthResult>;
|
||||
optionsSchema?: TSchema;
|
||||
create(options: z.output<TSchema>): SignInResolver<TAuthResult>;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function createSignInResolverFactory<
|
||||
TAuthResult,
|
||||
TOptionsOutput,
|
||||
TOptionsInput,
|
||||
TSchema extends ZodType = ZodType<unknown>,
|
||||
>(
|
||||
options: SignInResolverFactoryOptions<
|
||||
TAuthResult,
|
||||
TOptionsOutput,
|
||||
TOptionsInput
|
||||
>,
|
||||
): SignInResolverFactory<TAuthResult, TOptionsInput> {
|
||||
options: SignInResolverFactoryOptions<TAuthResult, TSchema>,
|
||||
): SignInResolverFactory<TAuthResult, z.input<TSchema>> {
|
||||
const { optionsSchema } = options;
|
||||
if (!optionsSchema) {
|
||||
return (resolverOptions?: TOptionsInput) => {
|
||||
return (resolverOptions?: z.input<TSchema>) => {
|
||||
if (resolverOptions) {
|
||||
throw new InputError('sign-in resolver does not accept options');
|
||||
}
|
||||
return options.create(undefined as TOptionsOutput);
|
||||
return options.create(undefined);
|
||||
};
|
||||
}
|
||||
const factory = (
|
||||
...[resolverOptions]: undefined extends TOptionsInput
|
||||
? [options?: TOptionsInput]
|
||||
: [options: TOptionsInput]
|
||||
...[resolverOptions]: undefined extends z.input<TSchema>
|
||||
? [options?: z.input<TSchema>]
|
||||
: [options: z.input<TSchema>]
|
||||
) => {
|
||||
let parsedOptions;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user