Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
chore: conditional types
  • Loading branch information
dreamorosi committed Jul 12, 2025
commit 646af576c710125992b05f87044070d6190cd343
12 changes: 5 additions & 7 deletions packages/parser/src/middleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,11 @@ import type { ParserOptions, ParserOutput } from '../types/parser.js';
*
* type Order = z.infer<typeof oderSchema>;
*
* export const handler = middy(
* async (event: Order, _context: unknown): Promise<void> => {
* // event is validated as sqs message envelope
* // the body is unwrapped and parsed into object ready to use
* // you can now use event as Order in your code
* }
* ).use(parser({ schema: oderSchema, envelope: sqsEnvelope }));
* export const handler = middy()
* .use(parser({ schema: oderSchema, envelope: sqsEnvelope }))
* .handler(async (event) => {
* // ^ event is inferred as Order[]
* })
* ```
*
* @param options - options for the parser
Expand Down
12 changes: 8 additions & 4 deletions packages/parser/src/parser.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { StandardSchemaV1 } from '@standard-schema/spec';
import { ParseError } from './errors.js';
import type { Envelope } from './types/index.js';
import type { ArrayEnvelope, Envelope } from './types/index.js';
import type { InferOutput, ParsedResult } from './types/parser.js';

/**
Expand Down Expand Up @@ -57,15 +57,17 @@ function parse<T extends StandardSchemaV1, E extends Envelope>(
envelope: E,
schema: T,
safeParse?: false
): InferOutput<T>;
): E extends ArrayEnvelope ? InferOutput<T>[] : InferOutput<T>;

// With envelope, with safeParse
function parse<T extends StandardSchemaV1, E extends Envelope>(
data: unknown,
envelope: E,
schema: T,
safeParse: true
): ParsedResult<unknown, InferOutput<T>>;
): E extends ArrayEnvelope
? ParsedResult<unknown, InferOutput<T>[]>
: ParsedResult<unknown, InferOutput<T>>;

// No envelope, with boolean | undefined safeParse
function parse<T extends StandardSchemaV1>(
Expand All @@ -81,7 +83,9 @@ function parse<T extends StandardSchemaV1, E extends Envelope>(
envelope: E,
schema: T,
safeParse?: boolean
): InferOutput<T> | ParsedResult<unknown, InferOutput<T>>;
): E extends ArrayEnvelope
? InferOutput<T>[] | ParsedResult<unknown, InferOutput<T>[]>
: InferOutput<T> | ParsedResult<unknown, InferOutput<T>>;

// Implementation
function parse<T extends StandardSchemaV1, E extends Envelope>(
Expand Down
2 changes: 1 addition & 1 deletion packages/parser/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export type {
ParseFunction,
ParserOptions,
} from '../types/parser.js';
export type { Envelope } from './envelope.js';
export type { ArrayEnvelope, Envelope, ObjectEnvelope } from './envelope.js';

export type {
ALBEvent,
Expand Down
95 changes: 32 additions & 63 deletions packages/parser/tests/types/parser.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,22 @@
import { describe } from 'node:test';
import { expectTypeOf, it } from 'vitest';
import middy from '@middy/core';
import { describe, expectTypeOf, it } from 'vitest';
import { z } from 'zod';
import { EventBridgeEnvelope } from '../../src/envelopes/eventbridge.js';
import { SqsEnvelope } from '../../src/envelopes/sqs.js';
import { JSONStringified } from '../../src/helpers/index.js';
import { parser } from '../../src/middleware/index.js';
import { parse } from '../../src/parser.js';
import type { EventBridgeEvent, SqsEvent } from '../../src/types/schema.js';
import { getTestEvent } from '../unit/helpers/utils.js';

describe('Parser types', () => {
const userSchema = z.object({
name: z.string(),
age: z.number(),
});
type User = z.infer<typeof userSchema>;
const input = { name: 'John', age: 30 };
const eventBridgeBaseEvent = getTestEvent<EventBridgeEvent>({
eventsPath: 'eventbridge',
filename: 'base',
});
const sqsBaseEvent = getTestEvent<SqsEvent>({
eventsPath: 'sqs',
filename: 'base',
});

it.each([
{
input,
case: 'when parsing successfully',
},
{
input: { name: 'John', age: '30' }, // Invalid input for User schema
case: 'when parsing fails',
},
])('infers return type for schema and safeParse $case', ({ input }) => {
it('infers return type for schema and safeParse', () => {
// Act
const result = parse(input, undefined, userSchema, true);
const result = parse({}, undefined, userSchema, true);

// Assess
if (result.success) {
Expand All @@ -48,64 +29,52 @@ describe('Parser types', () => {

it('infers return type for schema', () => {
// Act
const result = parse(input, undefined, userSchema);
const result = parse({}, undefined, userSchema);

// Assess
expectTypeOf(result).toEqualTypeOf<User>();
});

it('infers return type for schema and object envelope', () => {
// Prepare
const event = structuredClone(eventBridgeBaseEvent);
event.detail = input;

// Act
const result = parse(event, EventBridgeEnvelope, userSchema);
const result = parse({}, EventBridgeEnvelope, userSchema);

// Assess
expectTypeOf(result).toEqualTypeOf<User>();
});

it('infers return type for schema and array envelope', () => {
// Prepare
const event = structuredClone(sqsBaseEvent);
event.Records[0].body = JSON.stringify(input);
event.Records[1].body = JSON.stringify(input);

// Act
const result = parse(event, SqsEnvelope, JSONStringified(userSchema));
const result = parse({}, SqsEnvelope, JSONStringified(userSchema));

// Assess
expectTypeOf(result).toEqualTypeOf<User[]>();
});

it.each([
{
input,
case: 'when parsing successfully',
},
{
input: { name: 'John', age: '30' }, // Invalid input for User schema
case: 'when parsing fails',
},
])(
'infers return type for schema, object envelope and safeParse $case',
({ input }) => {
// Prepare
const event = structuredClone(eventBridgeBaseEvent);
event.detail = input;

// Act
const result = parse(event, EventBridgeEnvelope, userSchema, true);
it('infers return type for schema, object envelope and safeParse $case', () => {
// Act
const result = parse({}, EventBridgeEnvelope, userSchema, true);

// Assess
expectTypeOf(result.success).toEqualTypeOf<boolean>();
if (result.success) {
expectTypeOf(result.data).toEqualTypeOf<User>();
} else {
expectTypeOf(result.error).toEqualTypeOf<Error>();
expectTypeOf(result.originalEvent).toEqualTypeOf<unknown>();
}
// Assess
expectTypeOf(result.success).toEqualTypeOf<boolean>();
if (result.success) {
expectTypeOf(result.data).toEqualTypeOf<User>();
} else {
expectTypeOf(result.error).toEqualTypeOf<Error>();
expectTypeOf(result.originalEvent).toEqualTypeOf<unknown>();
}
);
});

it('infers the return type when using parse middleware', () => {
middy()
.use(
parser({
schema: JSONStringified(userSchema),
envelope: SqsEnvelope,
})
)
.handler(async (event) => {
expectTypeOf(event).toEqualTypeOf<User[]>();
});
});
});