From 5d3024f0f47ab4461f58d0305f504cdf2302b2f9 Mon Sep 17 00:00:00 2001 From: Alexander Schueren Date: Tue, 5 Nov 2024 20:36:48 +0100 Subject: [PATCH 1/5] feat(parser): add symbol property to envelope types for better type inference --- packages/parser/src/envelopes/apigw.ts | 1 + packages/parser/src/envelopes/apigwv2.ts | 1 + packages/parser/src/envelopes/cloudwatch.ts | 1 + packages/parser/src/envelopes/dynamodb.ts | 1 + packages/parser/src/envelopes/event-bridge.ts | 1 + packages/parser/src/envelopes/kafka.ts | 1 + .../parser/src/envelopes/kinesis-firehose.ts | 1 + packages/parser/src/envelopes/kinesis.ts | 1 + packages/parser/src/envelopes/lambda.ts | 1 + packages/parser/src/envelopes/sns.ts | 2 + packages/parser/src/envelopes/sqs.ts | 3 +- packages/parser/src/envelopes/vpc-lattice.ts | 1 + .../parser/src/envelopes/vpc-latticev2.ts | 1 + packages/parser/src/types/envelope.ts | 74 +++++++----------- packages/parser/src/types/parser.ts | 26 ++----- packages/parser/tests/unit/types.test.ts | 77 +++++++++++++++++++ 16 files changed, 129 insertions(+), 64 deletions(-) create mode 100644 packages/parser/tests/unit/types.test.ts diff --git a/packages/parser/src/envelopes/apigw.ts b/packages/parser/src/envelopes/apigw.ts index 8bfba6cdf6..26e857be71 100644 --- a/packages/parser/src/envelopes/apigw.ts +++ b/packages/parser/src/envelopes/apigw.ts @@ -8,6 +8,7 @@ import { Envelope } from './envelope.js'; * API Gateway envelope to extract data within body key */ export const ApiGatewayEnvelope = { + symbol: 'object' as const, parse(data: unknown, schema: T): z.infer { return Envelope.parse(APIGatewayProxyEventSchema.parse(data).body, schema); }, diff --git a/packages/parser/src/envelopes/apigwv2.ts b/packages/parser/src/envelopes/apigwv2.ts index db69794649..e629f9a893 100644 --- a/packages/parser/src/envelopes/apigwv2.ts +++ b/packages/parser/src/envelopes/apigwv2.ts @@ -8,6 +8,7 @@ import { Envelope } from './envelope.js'; * API Gateway V2 envelope to extract data within body key */ export const ApiGatewayV2Envelope = { + symbol: 'object' as const, parse(data: unknown, schema: T): z.infer { return Envelope.parse( APIGatewayProxyEventV2Schema.parse(data).body, diff --git a/packages/parser/src/envelopes/cloudwatch.ts b/packages/parser/src/envelopes/cloudwatch.ts index cb3112f4ff..25c2fe046b 100644 --- a/packages/parser/src/envelopes/cloudwatch.ts +++ b/packages/parser/src/envelopes/cloudwatch.ts @@ -14,6 +14,7 @@ import { Envelope } from './envelope.js'; * Note: The record will be parsed the same way so if model is str */ export const CloudWatchEnvelope = { + symbol: 'array' as const, parse(data: unknown, schema: T): z.infer[] { const parsedEnvelope = CloudWatchLogsSchema.parse(data); diff --git a/packages/parser/src/envelopes/dynamodb.ts b/packages/parser/src/envelopes/dynamodb.ts index ab9cc18c8c..067fc02c1c 100644 --- a/packages/parser/src/envelopes/dynamodb.ts +++ b/packages/parser/src/envelopes/dynamodb.ts @@ -12,6 +12,7 @@ import { Envelope } from './envelope.js'; * length of the list is the record's amount in the original event. */ export const DynamoDBStreamEnvelope = { + symbol: 'array' as const, parse( data: unknown, schema: T diff --git a/packages/parser/src/envelopes/event-bridge.ts b/packages/parser/src/envelopes/event-bridge.ts index 7c44bb9a56..c3b312ea1c 100644 --- a/packages/parser/src/envelopes/event-bridge.ts +++ b/packages/parser/src/envelopes/event-bridge.ts @@ -8,6 +8,7 @@ import { Envelope } from './envelope.js'; * Envelope for EventBridge schema that extracts and parses data from the `detail` key. */ export const EventBridgeEnvelope = { + symbol: 'object' as const, parse(data: unknown, schema: T): z.infer { return Envelope.parse(EventBridgeSchema.parse(data).detail, schema); }, diff --git a/packages/parser/src/envelopes/kafka.ts b/packages/parser/src/envelopes/kafka.ts index 4442834aff..12ed8d4445 100644 --- a/packages/parser/src/envelopes/kafka.ts +++ b/packages/parser/src/envelopes/kafka.ts @@ -17,6 +17,7 @@ import { Envelope } from './envelope.js'; */ export const KafkaEnvelope = { + symbol: 'array' as const, parse(data: unknown, schema: T): z.infer[] { // manually fetch event source to decide between Msk or SelfManaged const eventSource = (data as KafkaMskEvent).eventSource; diff --git a/packages/parser/src/envelopes/kinesis-firehose.ts b/packages/parser/src/envelopes/kinesis-firehose.ts index dd543f82b3..a3af7de3cd 100644 --- a/packages/parser/src/envelopes/kinesis-firehose.ts +++ b/packages/parser/src/envelopes/kinesis-firehose.ts @@ -17,6 +17,7 @@ import { Envelope } from './envelope.js'; * https://docs.aws.amazon.com/lambda/latest/dg/services-kinesisfirehose.html */ export const KinesisFirehoseEnvelope = { + symbol: 'array' as const, parse(data: unknown, schema: T): z.infer[] { const parsedEnvelope = KinesisFirehoseSchema.parse(data); diff --git a/packages/parser/src/envelopes/kinesis.ts b/packages/parser/src/envelopes/kinesis.ts index 480addb756..6c15c2c585 100644 --- a/packages/parser/src/envelopes/kinesis.ts +++ b/packages/parser/src/envelopes/kinesis.ts @@ -15,6 +15,7 @@ import { Envelope } from './envelope.js'; * all items in the list will be parsed as str and not as JSON (and vice versa) */ export const KinesisEnvelope = { + symbol: 'array' as const, parse(data: unknown, schema: T): z.infer[] { const parsedEnvelope = KinesisDataStreamSchema.parse(data); diff --git a/packages/parser/src/envelopes/lambda.ts b/packages/parser/src/envelopes/lambda.ts index 69bf1447c5..f9f6b65239 100644 --- a/packages/parser/src/envelopes/lambda.ts +++ b/packages/parser/src/envelopes/lambda.ts @@ -8,6 +8,7 @@ import { Envelope } from './envelope.js'; * Lambda function URL envelope to extract data within body key */ export const LambdaFunctionUrlEnvelope = { + symbol: 'object' as const, parse(data: unknown, schema: T): z.infer { const parsedEnvelope = LambdaFunctionUrlSchema.parse(data); diff --git a/packages/parser/src/envelopes/sns.ts b/packages/parser/src/envelopes/sns.ts index bf3f7aedf0..f9f5988809 100644 --- a/packages/parser/src/envelopes/sns.ts +++ b/packages/parser/src/envelopes/sns.ts @@ -15,6 +15,7 @@ import { Envelope } from './envelope.js'; * all items in the list will be parsed as str and npt as JSON (and vice versa) */ export const SnsEnvelope = { + symbol: 'array' as const, parse(data: unknown, schema: T): z.infer[] { const parsedEnvelope = SnsSchema.parse(data); @@ -70,6 +71,7 @@ export const SnsEnvelope = { * */ export const SnsSqsEnvelope = { + symbol: 'array' as const, parse(data: unknown, schema: T): z.infer { const parsedEnvelope = SqsSchema.parse(data); diff --git a/packages/parser/src/envelopes/sqs.ts b/packages/parser/src/envelopes/sqs.ts index 95de353b46..af3475747d 100644 --- a/packages/parser/src/envelopes/sqs.ts +++ b/packages/parser/src/envelopes/sqs.ts @@ -14,7 +14,8 @@ import { Envelope } from './envelope.js'; * all items in the list will be parsed as str and npt as JSON (and vice versa) */ export const SqsEnvelope = { - parse(data: unknown, schema: T): z.infer[] { + symbol: 'array' as const, + parse(data: unknown, schema: T): z.infer { const parsedEnvelope = SqsSchema.parse(data); return parsedEnvelope.Records.map((record) => { diff --git a/packages/parser/src/envelopes/vpc-lattice.ts b/packages/parser/src/envelopes/vpc-lattice.ts index 8c2124281b..26ae2e3436 100644 --- a/packages/parser/src/envelopes/vpc-lattice.ts +++ b/packages/parser/src/envelopes/vpc-lattice.ts @@ -9,6 +9,7 @@ import { Envelope } from './envelope.js'; */ export const VpcLatticeEnvelope = { + symbol: 'object' as const, parse(data: unknown, schema: T): z.infer { const parsedEnvelope = VpcLatticeSchema.parse(data); diff --git a/packages/parser/src/envelopes/vpc-latticev2.ts b/packages/parser/src/envelopes/vpc-latticev2.ts index a184b1c6fa..e40af419e6 100644 --- a/packages/parser/src/envelopes/vpc-latticev2.ts +++ b/packages/parser/src/envelopes/vpc-latticev2.ts @@ -8,6 +8,7 @@ import { Envelope } from './envelope.js'; * Amazon VPC Lattice envelope to extract data within body key */ export const VpcLatticeV2Envelope = { + symbol: 'object' as const, parse(data: unknown, schema: T): z.infer { const parsedEnvelope = VpcLatticeV2Schema.parse(data); diff --git a/packages/parser/src/types/envelope.ts b/packages/parser/src/types/envelope.ts index 8be125d144..dba9e67dfe 100644 --- a/packages/parser/src/types/envelope.ts +++ b/packages/parser/src/types/envelope.ts @@ -1,57 +1,41 @@ import type { ZodSchema, z } from 'zod'; -import type { - ApiGatewayEnvelope, - ApiGatewayV2Envelope, - CloudWatchEnvelope, - DynamoDBStreamEnvelope, - EventBridgeEnvelope, - KafkaEnvelope, - KinesisEnvelope, - KinesisFirehoseEnvelope, - LambdaFunctionUrlEnvelope, - SnsEnvelope, - SnsSqsEnvelope, - SqsEnvelope, - VpcLatticeEnvelope, - VpcLatticeV2Envelope, -} from '../envelopes/index.js'; +import type { ParsedResult } from './parser.js'; type DynamoDBStreamEnvelopeResponse = { NewImage: z.infer; OldImage: z.infer; }; -type Envelope = - | typeof ApiGatewayEnvelope - | typeof ApiGatewayV2Envelope - | typeof CloudWatchEnvelope - | typeof DynamoDBStreamEnvelope - | typeof EventBridgeEnvelope - | typeof KafkaEnvelope - | typeof KinesisEnvelope - | typeof KinesisFirehoseEnvelope - | typeof LambdaFunctionUrlEnvelope - | typeof SnsEnvelope - | typeof SnsSqsEnvelope - | typeof SqsEnvelope - | typeof VpcLatticeEnvelope - | typeof VpcLatticeV2Envelope - | undefined; +interface Envelope { + symbol: 'array' | 'object'; + parse( + data: unknown, + schema: T + ): z.infer | z.infer[]; + safeParse(data: unknown, schema: T): ParsedResult; +} -/** - * Envelopes that return an array, needed to narrow down the return type of the parser - */ -type EnvelopeArrayReturnType = - | typeof CloudWatchEnvelope - | typeof DynamoDBStreamEnvelope - | typeof KafkaEnvelope - | typeof KinesisEnvelope - | typeof KinesisFirehoseEnvelope - | typeof SnsEnvelope - | typeof SqsEnvelope; +interface ArrayEnvelope extends Omit { + symbol: 'array'; + parse(data: unknown, schema: T): z.infer[]; + safeParse( + data: unknown, + schema: T + ): ParsedResult[]>; +} + +interface ObjectEnvelope extends Omit { + symbol: 'object'; + parse(data: unknown, schema: T): z.infer; + safeParse( + data: unknown, + schema: T + ): ParsedResult>; +} export type { - Envelope, + ArrayEnvelope, DynamoDBStreamEnvelopeResponse, - EnvelopeArrayReturnType, + Envelope, + ObjectEnvelope, }; diff --git a/packages/parser/src/types/parser.ts b/packages/parser/src/types/parser.ts index c7fe891c6a..198f5c2309 100644 --- a/packages/parser/src/types/parser.ts +++ b/packages/parser/src/types/parser.ts @@ -1,5 +1,5 @@ import type { ZodError, ZodSchema, z } from 'zod'; -import type { Envelope, EnvelopeArrayReturnType } from './envelope.js'; +import type { ArrayEnvelope, Envelope } from './envelope.js'; /** * Options for the parser used in middy middleware and decorator @@ -44,20 +44,14 @@ type ParsedResult = type ZodInferredResult< TSchema extends ZodSchema, TEnvelope extends Envelope, -> = undefined extends TEnvelope - ? z.infer - : TEnvelope extends EnvelopeArrayReturnType - ? z.infer[] - : z.infer; +> = TEnvelope extends ArrayEnvelope ? z.infer[] : z.infer; type ZodInferredSafeParseResult< TSchema extends ZodSchema, TEnvelope extends Envelope, -> = undefined extends TEnvelope - ? ParsedResult> - : TEnvelope extends EnvelopeArrayReturnType - ? ParsedResult> - : ParsedResult[]>; +> = TEnvelope extends ArrayEnvelope + ? ParsedResult[]> + : ParsedResult>; /** * The output of the parser function, can be either schema inferred type or a ParsedResult @@ -66,13 +60,9 @@ type ParserOutput< TSchema extends ZodSchema, TEnvelope extends Envelope, TSafeParse = false, -> = undefined extends TSafeParse - ? ZodInferredResult - : TSafeParse extends true - ? ZodInferredSafeParseResult - : TSafeParse extends false - ? ZodInferredResult - : never; +> = TSafeParse extends true + ? ZodInferredSafeParseResult + : ZodInferredResult; export type { ParserOptions, diff --git a/packages/parser/tests/unit/types.test.ts b/packages/parser/tests/unit/types.test.ts new file mode 100644 index 0000000000..0d97570032 --- /dev/null +++ b/packages/parser/tests/unit/types.test.ts @@ -0,0 +1,77 @@ +import { z } from 'zod'; +import { + ApiGatewayEnvelope, + ApiGatewayV2Envelope, + CloudWatchEnvelope, + DynamoDBStreamEnvelope, + EventBridgeEnvelope, + KafkaEnvelope, + KinesisEnvelope, + KinesisFirehoseEnvelope, + LambdaFunctionUrlEnvelope, + SnsEnvelope, + SnsSqsEnvelope, + SqsEnvelope, + VpcLatticeEnvelope, + VpcLatticeV2Envelope, +} from '../../src/envelopes'; +import type { ParserOutput } from '../../src/types/parser'; + +describe('Types ', () => { + const userSchema = z.object({ + name: z.string(), + age: z.number(), + }); + + it.each([ + { envelope: ApiGatewayEnvelope, name: 'ApiGateway' }, + { envelope: ApiGatewayV2Envelope, name: 'ApiGatewayV2' }, + { envelope: EventBridgeEnvelope, name: 'EventBridge' }, + { envelope: LambdaFunctionUrlEnvelope, name: 'LambdaFunctionUrl' }, + { envelope: VpcLatticeEnvelope, name: 'VpcLattice' }, + { envelope: VpcLatticeV2Envelope, name: 'VpcLatticeV2' }, + ])('should infer object for $name envelope', (testCase) => { + type Result = ParserOutput; + // Define the expected type + + // This will fail TypeScript compilation if Result is is an array + const result = { name: 'John', age: 30 } satisfies Result; + + // Runtime checks to ensure it's an array with single element + expect(Array.isArray(result)).toBe(false); + expect(result).toEqual({ name: 'John', age: 30 }); + + // Type assertion to ensure it's specifically User[] + type AssertIsUserArray = T extends z.infer[] + ? true + : false; + type Test = AssertIsUserArray; + }); + + it.each([ + { envelope: CloudWatchEnvelope, name: 'CloudWatch' }, + { envelope: DynamoDBStreamEnvelope, name: 'DynamoDBStream' }, + { envelope: KafkaEnvelope, name: 'Kafka' }, + { envelope: KinesisFirehoseEnvelope, name: 'KinesisFirehose' }, + { envelope: KinesisEnvelope, name: 'Kinesis' }, + { envelope: SqsEnvelope, name: 'Sqs' }, + { envelope: SnsEnvelope, name: 'Sns' }, + { envelope: SnsSqsEnvelope, name: 'SnsSqs' }, + ])('should infer array type with $name envelope', (testCase) => { + // Define the expected type + type Result = ParserOutput; + + // This will fail TypeScript compilation if Result is is an array + const result = { name: 'John', age: 30 } satisfies Result; + + // Runtime checks to ensure it's an array with single element + expect(Array.isArray(result)).toBe(false); + expect(result).toEqual({ name: 'John', age: 30 }); + + // Type assertion to ensure it's specifically User[] + type AssertIsUserArray = T extends z.infer[] + ? true + : false; + type Test = AssertIsUserArray; + }); +}); From fc6469224362c4a3b680a104e24945dea2440041 Mon Sep 17 00:00:00 2001 From: Alexander Schueren Date: Tue, 5 Nov 2024 21:16:26 +0100 Subject: [PATCH 2/5] fix(parser): update safeParse return types for better type inference --- packages/parser/src/envelopes/apigw.ts | 7 +++++-- packages/parser/src/envelopes/apigwv2.ts | 5 ++++- packages/parser/src/envelopes/cloudwatch.ts | 5 ++++- packages/parser/src/envelopes/dynamodb.ts | 5 ++++- packages/parser/src/envelopes/envelope.ts | 2 +- packages/parser/src/envelopes/kafka.ts | 5 ++++- packages/parser/src/envelopes/kinesis-firehose.ts | 5 ++++- packages/parser/src/envelopes/kinesis.ts | 5 ++++- packages/parser/src/envelopes/sns.ts | 12 +++++++++--- packages/parser/src/envelopes/sqs.ts | 7 +++++-- packages/parser/src/middleware/parser.ts | 2 +- packages/parser/src/types/envelope.ts | 15 ++++----------- packages/parser/src/types/parser.ts | 14 ++++++++++---- packages/parser/tests/unit/types.test.ts | 6 +++--- 14 files changed, 62 insertions(+), 33 deletions(-) diff --git a/packages/parser/src/envelopes/apigw.ts b/packages/parser/src/envelopes/apigw.ts index 26e857be71..3b8d08f977 100644 --- a/packages/parser/src/envelopes/apigw.ts +++ b/packages/parser/src/envelopes/apigw.ts @@ -1,7 +1,7 @@ import type { ZodSchema, z } from 'zod'; import { ParseError } from '../errors.js'; import { APIGatewayProxyEventSchema } from '../schemas/apigw.js'; -import type { ParsedResult } from '../types'; +import type { ParsedResult } from '../types/index.js'; import { Envelope } from './envelope.js'; /** @@ -13,7 +13,10 @@ export const ApiGatewayEnvelope = { return Envelope.parse(APIGatewayProxyEventSchema.parse(data).body, schema); }, - safeParse(data: unknown, schema: T): ParsedResult { + safeParse( + data: unknown, + schema: T + ): ParsedResult> { const parsedEnvelope = APIGatewayProxyEventSchema.safeParse(data); if (!parsedEnvelope.success) { return { diff --git a/packages/parser/src/envelopes/apigwv2.ts b/packages/parser/src/envelopes/apigwv2.ts index e629f9a893..b58887f6db 100644 --- a/packages/parser/src/envelopes/apigwv2.ts +++ b/packages/parser/src/envelopes/apigwv2.ts @@ -16,7 +16,10 @@ export const ApiGatewayV2Envelope = { ); }, - safeParse(data: unknown, schema: T): ParsedResult { + safeParse( + data: unknown, + schema: T + ): ParsedResult> { const parsedEnvelope = APIGatewayProxyEventV2Schema.safeParse(data); if (!parsedEnvelope.success) { return { diff --git a/packages/parser/src/envelopes/cloudwatch.ts b/packages/parser/src/envelopes/cloudwatch.ts index 25c2fe046b..2f3fe9ec62 100644 --- a/packages/parser/src/envelopes/cloudwatch.ts +++ b/packages/parser/src/envelopes/cloudwatch.ts @@ -23,7 +23,10 @@ export const CloudWatchEnvelope = { }); }, - safeParse(data: unknown, schema: T): ParsedResult { + safeParse( + data: unknown, + schema: T + ): ParsedResult[]> { const parsedEnvelope = CloudWatchLogsSchema.safeParse(data); if (!parsedEnvelope.success) { diff --git a/packages/parser/src/envelopes/dynamodb.ts b/packages/parser/src/envelopes/dynamodb.ts index 067fc02c1c..f0dc890e30 100644 --- a/packages/parser/src/envelopes/dynamodb.ts +++ b/packages/parser/src/envelopes/dynamodb.ts @@ -27,7 +27,10 @@ export const DynamoDBStreamEnvelope = { }); }, - safeParse(data: unknown, schema: T): ParsedResult { + safeParse( + data: unknown, + schema: T + ): ParsedResult>[]> { const parsedEnvelope = DynamoDBStreamSchema.safeParse(data); if (!parsedEnvelope.success) { diff --git a/packages/parser/src/envelopes/envelope.ts b/packages/parser/src/envelopes/envelope.ts index 8a33e5d30b..5b3d0d7851 100644 --- a/packages/parser/src/envelopes/envelope.ts +++ b/packages/parser/src/envelopes/envelope.ts @@ -1,6 +1,6 @@ import type { ZodSchema, z } from 'zod'; import { ParseError } from '../errors.js'; -import type { ParsedResult } from '../types'; +import type { ParsedResult } from '../types/index.js'; export const Envelope = { /** diff --git a/packages/parser/src/envelopes/kafka.ts b/packages/parser/src/envelopes/kafka.ts index 12ed8d4445..69a3fa34ad 100644 --- a/packages/parser/src/envelopes/kafka.ts +++ b/packages/parser/src/envelopes/kafka.ts @@ -36,7 +36,10 @@ export const KafkaEnvelope = { }); }, - safeParse(data: unknown, schema: T): ParsedResult { + safeParse( + data: unknown, + schema: T + ): ParsedResult[]> { // manually fetch event source to deside between Msk or SelfManaged const eventSource = (data as KafkaMskEvent).eventSource; diff --git a/packages/parser/src/envelopes/kinesis-firehose.ts b/packages/parser/src/envelopes/kinesis-firehose.ts index a3af7de3cd..e63c04399d 100644 --- a/packages/parser/src/envelopes/kinesis-firehose.ts +++ b/packages/parser/src/envelopes/kinesis-firehose.ts @@ -26,7 +26,10 @@ export const KinesisFirehoseEnvelope = { }); }, - safeParse(data: unknown, schema: T): ParsedResult { + safeParse( + data: unknown, + schema: T + ): ParsedResult[]> { const parsedEnvelope = KinesisFirehoseSchema.safeParse(data); if (!parsedEnvelope.success) { diff --git a/packages/parser/src/envelopes/kinesis.ts b/packages/parser/src/envelopes/kinesis.ts index 6c15c2c585..230a5dc6d6 100644 --- a/packages/parser/src/envelopes/kinesis.ts +++ b/packages/parser/src/envelopes/kinesis.ts @@ -24,7 +24,10 @@ export const KinesisEnvelope = { }); }, - safeParse(data: unknown, schema: T): ParsedResult { + safeParse( + data: unknown, + schema: T + ): ParsedResult[]> { const parsedEnvelope = KinesisDataStreamSchema.safeParse(data); if (!parsedEnvelope.success) { return { diff --git a/packages/parser/src/envelopes/sns.ts b/packages/parser/src/envelopes/sns.ts index f9f5988809..0d7a258e9e 100644 --- a/packages/parser/src/envelopes/sns.ts +++ b/packages/parser/src/envelopes/sns.ts @@ -24,7 +24,10 @@ export const SnsEnvelope = { }); }, - safeParse(data: unknown, schema: T): ParsedResult { + safeParse( + data: unknown, + schema: T + ): ParsedResult[]> { const parsedEnvelope = SnsSchema.safeParse(data); if (!parsedEnvelope.success) { @@ -72,7 +75,7 @@ export const SnsEnvelope = { */ export const SnsSqsEnvelope = { symbol: 'array' as const, - parse(data: unknown, schema: T): z.infer { + parse(data: unknown, schema: T): z.infer[] { const parsedEnvelope = SqsSchema.parse(data); return parsedEnvelope.Records.map((record) => { @@ -84,7 +87,10 @@ export const SnsSqsEnvelope = { }); }, - safeParse(data: unknown, schema: T): ParsedResult { + safeParse( + data: unknown, + schema: T + ): ParsedResult[]> { const parsedEnvelope = SqsSchema.safeParse(data); if (!parsedEnvelope.success) { return { diff --git a/packages/parser/src/envelopes/sqs.ts b/packages/parser/src/envelopes/sqs.ts index af3475747d..ff67662621 100644 --- a/packages/parser/src/envelopes/sqs.ts +++ b/packages/parser/src/envelopes/sqs.ts @@ -15,7 +15,7 @@ import { Envelope } from './envelope.js'; */ export const SqsEnvelope = { symbol: 'array' as const, - parse(data: unknown, schema: T): z.infer { + parse(data: unknown, schema: T): z.infer[] { const parsedEnvelope = SqsSchema.parse(data); return parsedEnvelope.Records.map((record) => { @@ -23,7 +23,10 @@ export const SqsEnvelope = { }); }, - safeParse(data: unknown, schema: T): ParsedResult { + safeParse( + data: unknown, + schema: T + ): ParsedResult[]> { const parsedEnvelope = SqsSchema.safeParse(data); if (!parsedEnvelope.success) { return { diff --git a/packages/parser/src/middleware/parser.ts b/packages/parser/src/middleware/parser.ts index aa889125b5..3a31e0992f 100644 --- a/packages/parser/src/middleware/parser.ts +++ b/packages/parser/src/middleware/parser.ts @@ -35,7 +35,7 @@ import type { ParserOptions, ParserOutput } from '../types/parser.js'; */ const parser = < TSchema extends ZodType, - TEnvelope extends Envelope = undefined, + TEnvelope extends Envelope, TSafeParse extends boolean = false, >( options: ParserOptions diff --git a/packages/parser/src/types/envelope.ts b/packages/parser/src/types/envelope.ts index dba9e67dfe..43bce7c7e9 100644 --- a/packages/parser/src/types/envelope.ts +++ b/packages/parser/src/types/envelope.ts @@ -6,16 +6,7 @@ type DynamoDBStreamEnvelopeResponse = { OldImage: z.infer; }; -interface Envelope { - symbol: 'array' | 'object'; - parse( - data: unknown, - schema: T - ): z.infer | z.infer[]; - safeParse(data: unknown, schema: T): ParsedResult; -} - -interface ArrayEnvelope extends Omit { +interface ArrayEnvelope { symbol: 'array'; parse(data: unknown, schema: T): z.infer[]; safeParse( @@ -24,7 +15,7 @@ interface ArrayEnvelope extends Omit { ): ParsedResult[]>; } -interface ObjectEnvelope extends Omit { +interface ObjectEnvelope { symbol: 'object'; parse(data: unknown, schema: T): z.infer; safeParse( @@ -33,6 +24,8 @@ interface ObjectEnvelope extends Omit { ): ParsedResult>; } +type Envelope = ArrayEnvelope | ObjectEnvelope | undefined; + export type { ArrayEnvelope, DynamoDBStreamEnvelopeResponse, diff --git a/packages/parser/src/types/parser.ts b/packages/parser/src/types/parser.ts index 198f5c2309..16178fa958 100644 --- a/packages/parser/src/types/parser.ts +++ b/packages/parser/src/types/parser.ts @@ -44,14 +44,20 @@ type ParsedResult = type ZodInferredResult< TSchema extends ZodSchema, TEnvelope extends Envelope, -> = TEnvelope extends ArrayEnvelope ? z.infer[] : z.infer; +> = undefined extends TEnvelope + ? z.infer + : TEnvelope extends ArrayEnvelope + ? z.infer[] + : z.infer; type ZodInferredSafeParseResult< TSchema extends ZodSchema, TEnvelope extends Envelope, -> = TEnvelope extends ArrayEnvelope - ? ParsedResult[]> - : ParsedResult>; +> = undefined extends TEnvelope + ? ParsedResult> + : TEnvelope extends ArrayEnvelope + ? ParsedResult[]> + : ParsedResult>; /** * The output of the parser function, can be either schema inferred type or a ParsedResult diff --git a/packages/parser/tests/unit/types.test.ts b/packages/parser/tests/unit/types.test.ts index 0d97570032..6e258a1790 100644 --- a/packages/parser/tests/unit/types.test.ts +++ b/packages/parser/tests/unit/types.test.ts @@ -62,11 +62,11 @@ describe('Types ', () => { type Result = ParserOutput; // This will fail TypeScript compilation if Result is is an array - const result = { name: 'John', age: 30 } satisfies Result; + const result = [{ name: 'John', age: 30 }] satisfies Result; // Runtime checks to ensure it's an array with single element - expect(Array.isArray(result)).toBe(false); - expect(result).toEqual({ name: 'John', age: 30 }); + expect(Array.isArray(result)).toBe(true); + expect(result).toEqual([{ name: 'John', age: 30 }]); // Type assertion to ensure it's specifically User[] type AssertIsUserArray = T extends z.infer[] From b48e1c896f6ed31f2d951a4e7bceabadcb53a3ad Mon Sep 17 00:00:00 2001 From: Alexander Schueren Date: Thu, 7 Nov 2024 09:26:03 +0100 Subject: [PATCH 3/5] Update packages/parser/tests/unit/types.test.ts Co-authored-by: Andrea Amorosi --- packages/parser/tests/unit/types.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/parser/tests/unit/types.test.ts b/packages/parser/tests/unit/types.test.ts index 6e258a1790..c975921fd9 100644 --- a/packages/parser/tests/unit/types.test.ts +++ b/packages/parser/tests/unit/types.test.ts @@ -14,7 +14,7 @@ import { SqsEnvelope, VpcLatticeEnvelope, VpcLatticeV2Envelope, -} from '../../src/envelopes'; +} from '../../src/envelopes/index.js'; import type { ParserOutput } from '../../src/types/parser'; describe('Types ', () => { From b396af2844792312bf68df2d8b80dfae61b591b4 Mon Sep 17 00:00:00 2001 From: Alexander Schueren Date: Thu, 7 Nov 2024 09:26:10 +0100 Subject: [PATCH 4/5] Update packages/parser/tests/unit/types.test.ts Co-authored-by: Andrea Amorosi --- packages/parser/tests/unit/types.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/parser/tests/unit/types.test.ts b/packages/parser/tests/unit/types.test.ts index c975921fd9..344a3afd0d 100644 --- a/packages/parser/tests/unit/types.test.ts +++ b/packages/parser/tests/unit/types.test.ts @@ -15,7 +15,7 @@ import { VpcLatticeEnvelope, VpcLatticeV2Envelope, } from '../../src/envelopes/index.js'; -import type { ParserOutput } from '../../src/types/parser'; +import type { ParserOutput } from '../../src/types/parser.js'; describe('Types ', () => { const userSchema = z.object({ From 8e229f2b9200ede91c34f87f4e33b4cf2b508db1 Mon Sep 17 00:00:00 2001 From: Alexander Schueren Date: Thu, 7 Nov 2024 09:49:43 +0100 Subject: [PATCH 5/5] chore(parser): hide discriminator from IDE autocomplete --- packages/parser/src/envelopes/apigw.ts | 8 ++++++-- packages/parser/src/envelopes/apigwv2.ts | 8 ++++++-- packages/parser/src/envelopes/cloudwatch.ts | 8 ++++++-- packages/parser/src/envelopes/dynamodb.ts | 8 ++++++-- packages/parser/src/envelopes/envelope.ts | 10 +++++++++- packages/parser/src/envelopes/event-bridge.ts | 8 ++++++-- packages/parser/src/envelopes/kafka.ts | 8 ++++++-- packages/parser/src/envelopes/kinesis-firehose.ts | 8 ++++++-- packages/parser/src/envelopes/kinesis.ts | 8 ++++++-- packages/parser/src/envelopes/lambda.ts | 8 ++++++-- packages/parser/src/envelopes/sns.ts | 14 +++++++++++--- packages/parser/src/envelopes/sqs.ts | 8 ++++++-- packages/parser/src/envelopes/vpc-lattice.ts | 8 ++++++-- packages/parser/src/envelopes/vpc-latticev2.ts | 8 ++++++-- packages/parser/src/types/envelope.ts | 5 +++-- 15 files changed, 95 insertions(+), 30 deletions(-) diff --git a/packages/parser/src/envelopes/apigw.ts b/packages/parser/src/envelopes/apigw.ts index 9fd3f006b8..86f716b234 100644 --- a/packages/parser/src/envelopes/apigw.ts +++ b/packages/parser/src/envelopes/apigw.ts @@ -2,13 +2,17 @@ import type { ZodSchema, z } from 'zod'; import { ParseError } from '../errors.js'; import { APIGatewayProxyEventSchema } from '../schemas/apigw.js'; import type { ParsedResult } from '../types/parser.js'; -import { Envelope } from './envelope.js'; +import { Envelope, envelopeDiscriminator } from './envelope.js'; /** * API Gateway envelope to extract data within body key */ export const ApiGatewayEnvelope = { - symbol: 'object' as const, + /** + * This is a discriminator to differentiate whether an envelope returns an array or an object + * @hidden + */ + [envelopeDiscriminator]: 'object' as const, parse(data: unknown, schema: T): z.infer { return Envelope.parse(APIGatewayProxyEventSchema.parse(data).body, schema); }, diff --git a/packages/parser/src/envelopes/apigwv2.ts b/packages/parser/src/envelopes/apigwv2.ts index b58887f6db..45c80f0727 100644 --- a/packages/parser/src/envelopes/apigwv2.ts +++ b/packages/parser/src/envelopes/apigwv2.ts @@ -2,13 +2,17 @@ import type { ZodSchema, z } from 'zod'; import { ParseError } from '../errors.js'; import { APIGatewayProxyEventV2Schema } from '../schemas/apigwv2.js'; import type { ParsedResult } from '../types/index.js'; -import { Envelope } from './envelope.js'; +import { Envelope, envelopeDiscriminator } from './envelope.js'; /** * API Gateway V2 envelope to extract data within body key */ export const ApiGatewayV2Envelope = { - symbol: 'object' as const, + /** + * This is a discriminator to differentiate whether an envelope returns an array or an object + * @hidden + */ + [envelopeDiscriminator]: 'object' as const, parse(data: unknown, schema: T): z.infer { return Envelope.parse( APIGatewayProxyEventV2Schema.parse(data).body, diff --git a/packages/parser/src/envelopes/cloudwatch.ts b/packages/parser/src/envelopes/cloudwatch.ts index 2f3fe9ec62..4bdde3c9a4 100644 --- a/packages/parser/src/envelopes/cloudwatch.ts +++ b/packages/parser/src/envelopes/cloudwatch.ts @@ -2,7 +2,7 @@ import type { ZodSchema, z } from 'zod'; import { ParseError } from '../errors.js'; import { CloudWatchLogsSchema } from '../schemas/index.js'; import type { ParsedResult } from '../types/index.js'; -import { Envelope } from './envelope.js'; +import { Envelope, envelopeDiscriminator } from './envelope.js'; /** * CloudWatch Envelope to extract a List of log records. @@ -14,7 +14,11 @@ import { Envelope } from './envelope.js'; * Note: The record will be parsed the same way so if model is str */ export const CloudWatchEnvelope = { - symbol: 'array' as const, + /** + * This is a discriminator to differentiate whether an envelope returns an array or an object + * @hidden + */ + [envelopeDiscriminator]: 'array' as const, parse(data: unknown, schema: T): z.infer[] { const parsedEnvelope = CloudWatchLogsSchema.parse(data); diff --git a/packages/parser/src/envelopes/dynamodb.ts b/packages/parser/src/envelopes/dynamodb.ts index f0dc890e30..1a499d216f 100644 --- a/packages/parser/src/envelopes/dynamodb.ts +++ b/packages/parser/src/envelopes/dynamodb.ts @@ -3,7 +3,7 @@ import { ParseError } from '../errors.js'; import { DynamoDBStreamSchema } from '../schemas/index.js'; import type { DynamoDBStreamEnvelopeResponse } from '../types/envelope.js'; import type { ParsedResult, ParsedResultError } from '../types/index.js'; -import { Envelope } from './envelope.js'; +import { Envelope, envelopeDiscriminator } from './envelope.js'; /** * DynamoDB Stream Envelope to extract data within NewImage/OldImage @@ -12,7 +12,11 @@ import { Envelope } from './envelope.js'; * length of the list is the record's amount in the original event. */ export const DynamoDBStreamEnvelope = { - symbol: 'array' as const, + /** + * This is a discriminator to differentiate whether an envelope returns an array or an object + * @hidden + */ + [envelopeDiscriminator]: 'array' as const, parse( data: unknown, schema: T diff --git a/packages/parser/src/envelopes/envelope.ts b/packages/parser/src/envelopes/envelope.ts index 79b7de7651..df802b65af 100644 --- a/packages/parser/src/envelopes/envelope.ts +++ b/packages/parser/src/envelopes/envelope.ts @@ -2,7 +2,7 @@ import type { ZodSchema, z } from 'zod'; import { ParseError } from '../errors.js'; import type { ParsedResult } from '../types/parser.js'; -export const Envelope = { +const Envelope = { /** * Abstract function to parse the content of the envelope using provided schema. * Both inputs are provided as unknown by the user. @@ -67,3 +67,11 @@ export const Envelope = { } }, }; + +/** + * This is a discriminator to differentiate whether an envelope returns an array or an object + * @hidden + */ +const envelopeDiscriminator = Symbol.for('returnType'); + +export { Envelope, envelopeDiscriminator }; diff --git a/packages/parser/src/envelopes/event-bridge.ts b/packages/parser/src/envelopes/event-bridge.ts index c3b312ea1c..84f87f10db 100644 --- a/packages/parser/src/envelopes/event-bridge.ts +++ b/packages/parser/src/envelopes/event-bridge.ts @@ -2,13 +2,17 @@ import type { ZodSchema, z } from 'zod'; import { ParseError } from '../errors.js'; import { EventBridgeSchema } from '../schemas/index.js'; import type { ParsedResult } from '../types/index.js'; -import { Envelope } from './envelope.js'; +import { Envelope, envelopeDiscriminator } from './envelope.js'; /** * Envelope for EventBridge schema that extracts and parses data from the `detail` key. */ export const EventBridgeEnvelope = { - symbol: 'object' as const, + /** + * This is a discriminator to differentiate whether an envelope returns an array or an object + * @hidden + */ + [envelopeDiscriminator]: 'object' as const, parse(data: unknown, schema: T): z.infer { return Envelope.parse(EventBridgeSchema.parse(data).detail, schema); }, diff --git a/packages/parser/src/envelopes/kafka.ts b/packages/parser/src/envelopes/kafka.ts index 69a3fa34ad..f0f0e36844 100644 --- a/packages/parser/src/envelopes/kafka.ts +++ b/packages/parser/src/envelopes/kafka.ts @@ -5,7 +5,7 @@ import { KafkaSelfManagedEventSchema, } from '../schemas/kafka.js'; import type { KafkaMskEvent, ParsedResult } from '../types/index.js'; -import { Envelope } from './envelope.js'; +import { Envelope, envelopeDiscriminator } from './envelope.js'; /** * Kafka event envelope to extract data within body key @@ -17,7 +17,11 @@ import { Envelope } from './envelope.js'; */ export const KafkaEnvelope = { - symbol: 'array' as const, + /** + * This is a discriminator to differentiate whether an envelope returns an array or an object + * @hidden + */ + [envelopeDiscriminator]: 'array' as const, parse(data: unknown, schema: T): z.infer[] { // manually fetch event source to decide between Msk or SelfManaged const eventSource = (data as KafkaMskEvent).eventSource; diff --git a/packages/parser/src/envelopes/kinesis-firehose.ts b/packages/parser/src/envelopes/kinesis-firehose.ts index e63c04399d..6f03bcd331 100644 --- a/packages/parser/src/envelopes/kinesis-firehose.ts +++ b/packages/parser/src/envelopes/kinesis-firehose.ts @@ -2,7 +2,7 @@ import type { ZodSchema, z } from 'zod'; import { ParseError } from '../errors.js'; import { KinesisFirehoseSchema } from '../schemas/index.js'; import type { ParsedResult } from '../types/index.js'; -import { Envelope } from './envelope.js'; +import { Envelope, envelopeDiscriminator } from './envelope.js'; /** * Kinesis Firehose Envelope to extract array of Records @@ -17,7 +17,11 @@ import { Envelope } from './envelope.js'; * https://docs.aws.amazon.com/lambda/latest/dg/services-kinesisfirehose.html */ export const KinesisFirehoseEnvelope = { - symbol: 'array' as const, + /** + * This is a discriminator to differentiate whether an envelope returns an array or an object + * @hidden + */ + [envelopeDiscriminator]: 'array' as const, parse(data: unknown, schema: T): z.infer[] { const parsedEnvelope = KinesisFirehoseSchema.parse(data); diff --git a/packages/parser/src/envelopes/kinesis.ts b/packages/parser/src/envelopes/kinesis.ts index 230a5dc6d6..248b753acf 100644 --- a/packages/parser/src/envelopes/kinesis.ts +++ b/packages/parser/src/envelopes/kinesis.ts @@ -2,7 +2,7 @@ import type { ZodSchema, z } from 'zod'; import { ParseError } from '../errors.js'; import { KinesisDataStreamSchema } from '../schemas/kinesis.js'; import type { ParsedResult } from '../types/index.js'; -import { Envelope } from './envelope.js'; +import { Envelope, envelopeDiscriminator } from './envelope.js'; /** * Kinesis Data Stream Envelope to extract array of Records @@ -15,7 +15,11 @@ import { Envelope } from './envelope.js'; * all items in the list will be parsed as str and not as JSON (and vice versa) */ export const KinesisEnvelope = { - symbol: 'array' as const, + /** + * This is a discriminator to differentiate whether an envelope returns an array or an object + * @hidden + */ + [envelopeDiscriminator]: 'array' as const, parse(data: unknown, schema: T): z.infer[] { const parsedEnvelope = KinesisDataStreamSchema.parse(data); diff --git a/packages/parser/src/envelopes/lambda.ts b/packages/parser/src/envelopes/lambda.ts index f9f6b65239..ddee609064 100644 --- a/packages/parser/src/envelopes/lambda.ts +++ b/packages/parser/src/envelopes/lambda.ts @@ -2,13 +2,17 @@ import type { ZodSchema, z } from 'zod'; import { ParseError } from '../errors.js'; import { LambdaFunctionUrlSchema } from '../schemas/index.js'; import type { ParsedResult } from '../types/index.js'; -import { Envelope } from './envelope.js'; +import { Envelope, envelopeDiscriminator } from './envelope.js'; /** * Lambda function URL envelope to extract data within body key */ export const LambdaFunctionUrlEnvelope = { - symbol: 'object' as const, + /** + * This is a discriminator to differentiate whether an envelope returns an array or an object + * @hidden + */ + [envelopeDiscriminator]: 'object' as const, parse(data: unknown, schema: T): z.infer { const parsedEnvelope = LambdaFunctionUrlSchema.parse(data); diff --git a/packages/parser/src/envelopes/sns.ts b/packages/parser/src/envelopes/sns.ts index 0d7a258e9e..183cc84470 100644 --- a/packages/parser/src/envelopes/sns.ts +++ b/packages/parser/src/envelopes/sns.ts @@ -3,7 +3,7 @@ import { ParseError } from '../errors.js'; import { SnsSchema, SnsSqsNotificationSchema } from '../schemas/sns.js'; import { SqsSchema } from '../schemas/sqs.js'; import type { ParsedResult } from '../types/index.js'; -import { Envelope } from './envelope.js'; +import { Envelope, envelopeDiscriminator } from './envelope.js'; /** * SNS Envelope to extract array of Records @@ -15,7 +15,11 @@ import { Envelope } from './envelope.js'; * all items in the list will be parsed as str and npt as JSON (and vice versa) */ export const SnsEnvelope = { - symbol: 'array' as const, + /** + * This is a discriminator to differentiate whether an envelope returns an array or an object + * @hidden + */ + [envelopeDiscriminator]: 'array' as const, parse(data: unknown, schema: T): z.infer[] { const parsedEnvelope = SnsSchema.parse(data); @@ -74,7 +78,11 @@ export const SnsEnvelope = { * */ export const SnsSqsEnvelope = { - symbol: 'array' as const, + /** + * This is a discriminator to differentiate whether an envelope returns an array or an object + * @hidden + */ + [envelopeDiscriminator]: 'array' as const, parse(data: unknown, schema: T): z.infer[] { const parsedEnvelope = SqsSchema.parse(data); diff --git a/packages/parser/src/envelopes/sqs.ts b/packages/parser/src/envelopes/sqs.ts index ff67662621..c7bf36a9d1 100644 --- a/packages/parser/src/envelopes/sqs.ts +++ b/packages/parser/src/envelopes/sqs.ts @@ -2,7 +2,7 @@ import type { ZodSchema, z } from 'zod'; import { ParseError } from '../errors.js'; import { SqsSchema } from '../schemas/sqs.js'; import type { ParsedResult } from '../types/index.js'; -import { Envelope } from './envelope.js'; +import { Envelope, envelopeDiscriminator } from './envelope.js'; /** * SQS Envelope to extract array of Records @@ -14,7 +14,11 @@ import { Envelope } from './envelope.js'; * all items in the list will be parsed as str and npt as JSON (and vice versa) */ export const SqsEnvelope = { - symbol: 'array' as const, + /** + * This is a discriminator to differentiate whether an envelope returns an array or an object + * @hidden + */ + [envelopeDiscriminator]: 'array' as const, parse(data: unknown, schema: T): z.infer[] { const parsedEnvelope = SqsSchema.parse(data); diff --git a/packages/parser/src/envelopes/vpc-lattice.ts b/packages/parser/src/envelopes/vpc-lattice.ts index 26ae2e3436..c4665dca73 100644 --- a/packages/parser/src/envelopes/vpc-lattice.ts +++ b/packages/parser/src/envelopes/vpc-lattice.ts @@ -2,14 +2,18 @@ import type { ZodSchema, z } from 'zod'; import { ParseError } from '../errors.js'; import { VpcLatticeSchema } from '../schemas/index.js'; import type { ParsedResult } from '../types/index.js'; -import { Envelope } from './envelope.js'; +import { Envelope, envelopeDiscriminator } from './envelope.js'; /** * Amazon VPC Lattice envelope to extract data within body key */ export const VpcLatticeEnvelope = { - symbol: 'object' as const, + /** + * This is a discriminator to differentiate whether an envelope returns an array or an object + * @hidden + */ + [envelopeDiscriminator]: 'object' as const, parse(data: unknown, schema: T): z.infer { const parsedEnvelope = VpcLatticeSchema.parse(data); diff --git a/packages/parser/src/envelopes/vpc-latticev2.ts b/packages/parser/src/envelopes/vpc-latticev2.ts index e40af419e6..caeedf650a 100644 --- a/packages/parser/src/envelopes/vpc-latticev2.ts +++ b/packages/parser/src/envelopes/vpc-latticev2.ts @@ -2,13 +2,17 @@ import type { ZodSchema, z } from 'zod'; import { ParseError } from '../errors.js'; import { VpcLatticeV2Schema } from '../schemas/index.js'; import type { ParsedResult } from '../types/index.js'; -import { Envelope } from './envelope.js'; +import { Envelope, envelopeDiscriminator } from './envelope.js'; /** * Amazon VPC Lattice envelope to extract data within body key */ export const VpcLatticeV2Envelope = { - symbol: 'object' as const, + /** + * This is a discriminator to differentiate whether an envelope returns an array or an object + * @hidden + */ + [envelopeDiscriminator]: 'object' as const, parse(data: unknown, schema: T): z.infer { const parsedEnvelope = VpcLatticeV2Schema.parse(data); diff --git a/packages/parser/src/types/envelope.ts b/packages/parser/src/types/envelope.ts index 43bce7c7e9..6d8e16ede3 100644 --- a/packages/parser/src/types/envelope.ts +++ b/packages/parser/src/types/envelope.ts @@ -1,4 +1,5 @@ import type { ZodSchema, z } from 'zod'; +import type { envelopeDiscriminator } from '../envelopes/envelope.js'; import type { ParsedResult } from './parser.js'; type DynamoDBStreamEnvelopeResponse = { @@ -7,7 +8,7 @@ type DynamoDBStreamEnvelopeResponse = { }; interface ArrayEnvelope { - symbol: 'array'; + [envelopeDiscriminator]: 'array'; parse(data: unknown, schema: T): z.infer[]; safeParse( data: unknown, @@ -16,7 +17,7 @@ interface ArrayEnvelope { } interface ObjectEnvelope { - symbol: 'object'; + [envelopeDiscriminator]: 'object'; parse(data: unknown, schema: T): z.infer; safeParse( data: unknown,