Skip to content

Commit

Permalink
feat(Simulations): make simulations generic to infer payload based on…
Browse files Browse the repository at this point in the history
… type
  • Loading branch information
danbillson committed Oct 8, 2024
1 parent 46d9b2f commit be3ae08
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 33 deletions.
8 changes: 4 additions & 4 deletions src/__tests__/mocks/resources/simulations.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ISimulationResponse } from '../../../types';
import { Response, ResponsePaginated } from '../../../internal';
import { CreateSimulationRequestBody, UpdateSimulationRequestBody } from '../../../resources';

export const CreateSimulationMock: CreateSimulationRequestBody = {
export const CreateSimulationMock: CreateSimulationRequestBody<'address.created'> = {
notificationSettingId: 'ntfset_01gt21c5pdx9q1e4mh1xrsjjn6',
type: 'address.created',
name: 'New address created',
Expand All @@ -18,7 +18,7 @@ export const updateSimulationMock: UpdateSimulationRequestBody = {
name: 'New UK address created',
};

export const SimulationMock: ISimulationResponse = {
export const SimulationMock: ISimulationResponse<'address.created'> = {
id: 'ntfsim_01ghbkd0frb9k95cnhwd1bxpvk',
status: 'active',
notification_setting_id: 'ntfset_01gt21c5pdx9q1e4mh1xrsjjn6',
Expand All @@ -30,14 +30,14 @@ export const SimulationMock: ISimulationResponse = {
updated_at: '2024-10-13T07:20:50.52Z',
};

export const SimulationMockResponse: Response<ISimulationResponse> = {
export const SimulationMockResponse: Response<ISimulationResponse<'address.created'>> = {
data: SimulationMock,
meta: {
request_id: '',
},
};

export const ListSimulationMockResponse: ResponsePaginated<ISimulationResponse> = {
export const ListSimulationMockResponse: ResponsePaginated<ISimulationResponse<'address.created'>> = {
data: [SimulationMock],
meta: {
request_id: '',
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/resources/simulations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe('SimulationsResource', () => {
});

test('should create a new simulation', async () => {
const newSimulation: CreateSimulationRequestBody = CreateSimulationMock;
const newSimulation: CreateSimulationRequestBody<'address.created'> = CreateSimulationMock;
const paddleInstance = getPaddleTestClient();

paddleInstance.post = jest.fn().mockResolvedValue(SimulationMockResponse);
Expand Down
9 changes: 7 additions & 2 deletions src/entities/simulation/simulation-collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@
import { Simulation } from '../../entities';
import { type ISimulationResponse } from '../../types';
import { Collection } from '../../internal/base';
import type { IEventName } from '../../notifications';
import type { SimulationScenarioType } from '../../enums';

export class SimulationCollection extends Collection<ISimulationResponse, Simulation> {
override fromJson(data: ISimulationResponse): Simulation {
export class SimulationCollection<T extends IEventName | SimulationScenarioType> extends Collection<
ISimulationResponse<T>,
Simulation<T>
> {
override fromJson(data: ISimulationResponse<T>): Simulation<T> {
return new Simulation(data);
}
}
10 changes: 5 additions & 5 deletions src/entities/simulation/simulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@

import { type ISimulationResponse } from '../../types';
import type { SimulationScenarioType, Status } from '../../enums';
import type { IEventName } from '../../notifications';
import type { EventMap, IEventName } from '../../notifications';

export class Simulation {
export class Simulation<T extends IEventName | SimulationScenarioType> {
public readonly id: string;
public readonly status: Status;
public readonly notificationSettingId: string;
public readonly name: string;
public readonly type: IEventName | SimulationScenarioType;
public readonly payload: any;
public readonly type: T;
public readonly payload: (T extends IEventName ? EventMap[T] : null) | null;
public readonly lastRunAt: string | null;
public readonly createdAt: string;
public readonly updatedAt: string;

constructor(simulationResponse: ISimulationResponse) {
constructor(simulationResponse: ISimulationResponse<T>) {
this.id = simulationResponse.id;
this.status = simulationResponse.status;
this.notificationSettingId = simulationResponse.notification_setting_id;
Expand Down
49 changes: 49 additions & 0 deletions src/notifications/helpers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export enum EventName {
ReportCreated = 'report.created',
ReportUpdated = 'report.updated',
}

export type IEventName =
| 'address.created'
| 'address.updated'
Expand Down Expand Up @@ -175,3 +176,51 @@ export type IEventName =
| 'transaction.updated'
| 'report.created'
| 'report.updated';

export interface EventDataMap extends Record<IEventName, EventEntity> {
'address.created': AddressCreatedEvent;
'address.updated': AddressUpdatedEvent;
'address.imported': AddressImportedEvent;
'adjustment.created': AdjustmentCreatedEvent;
'adjustment.updated': AdjustmentUpdatedEvent;
'business.created': BusinessCreatedEvent;
'business.updated': BusinessUpdatedEvent;
'business.imported': BusinessImportedEvent;
'customer.created': CustomerCreatedEvent;
'customer.updated': CustomerUpdatedEvent;
'customer.imported': CustomerImportedEvent;
'discount.created': DiscountCreatedEvent;
'discount.updated': DiscountUpdatedEvent;
'discount.imported': DiscountImportedEvent;
'payout.created': PayoutCreatedEvent;
'payout.paid': PayoutPaidEvent;
'price.created': PriceCreatedEvent;
'price.updated': PriceUpdatedEvent;
'price.imported': PriceImportedEvent;
'product.created': ProductCreatedEvent;
'product.updated': ProductUpdatedEvent;
'product.imported': ProductImportedEvent;
'subscription.created': SubscriptionCreatedEvent;
'subscription.activated': SubscriptionActivatedEvent;
'subscription.canceled': SubscriptionCanceledEvent;
'subscription.imported': SubscriptionImportedEvent;
'subscription.paused': SubscriptionPausedEvent;
'subscription.resumed': SubscriptionResumedEvent;
'subscription.trialing': SubscriptionTrialingEvent;
'subscription.updated': SubscriptionUpdatedEvent;
'transaction.billed': TransactionBilledEvent;
'transaction.canceled': TransactionCanceledEvent;
'transaction.completed': TransactionCompletedEvent;
'transaction.created': TransactionCreatedEvent;
'transaction.paid': TransactionPaidEvent;
'transaction.past_due': TransactionPastDueEvent;
'transaction.payment_failed': TransactionPaymentFailedEvent;
'transaction.ready': TransactionReadyEvent;
'transaction.updated': TransactionUpdatedEvent;
'report.created': ReportCreatedEvent;
'report.updated': ReportUpdatedEvent;
}

export type EventMap = {
[K in keyof EventDataMap]: EventDataMap[K] extends { data: infer D } ? Partial<D> : never;
};
37 changes: 24 additions & 13 deletions src/resources/simulations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
*/

import { Simulation, SimulationCollection } from '../../entities';
import type { SimulationScenarioType } from '../../enums';
import { type ErrorResponse, type Response } from '../../internal';
import { BaseResource, PathParameters, QueryParameters } from '../../internal/base';
import type { IEventName } from '../../notifications';
import { type ISimulationResponse } from '../../types';
import {
type CreateSimulationRequestBody,
Expand All @@ -24,45 +26,54 @@ const SimulationPaths = {
} as const;

export class SimulationsResource extends BaseResource {
public list(queryParams?: ListSimulationQueryParameters): SimulationCollection {
public list<T extends IEventName | SimulationScenarioType>(
queryParams?: ListSimulationQueryParameters,
): SimulationCollection<T> {
const queryParameters = new QueryParameters(queryParams);
return new SimulationCollection(this.client, SimulationPaths.list + queryParameters.toQueryString());
}

public async create(createSimulationParameters: CreateSimulationRequestBody): Promise<Simulation> {
const response = await this.client.post<CreateSimulationRequestBody, Response<ISimulationResponse> | ErrorResponse>(
SimulationPaths.create,
createSimulationParameters,
);
public async create<T extends IEventName | SimulationScenarioType>(
createSimulationParameters: CreateSimulationRequestBody<T>,
): Promise<Simulation<T>> {
const response = await this.client.post<
CreateSimulationRequestBody<T>,
Response<ISimulationResponse<T>> | ErrorResponse
>(SimulationPaths.create, createSimulationParameters);

const data = this.handleResponse<ISimulationResponse>(response);
const data = this.handleResponse<ISimulationResponse<T>>(response);

return new Simulation(data);
}

public async get(simulationId: string): Promise<Simulation> {
public async get<T extends IEventName | SimulationScenarioType>(simulationId: string): Promise<Simulation<T>> {
const urlWithPathParams = new PathParameters(SimulationPaths.get, {
simulation_id: simulationId,
}).deriveUrl();

const response = await this.client.get<undefined, Response<ISimulationResponse> | ErrorResponse>(urlWithPathParams);
const response = await this.client.get<undefined, Response<ISimulationResponse<T>> | ErrorResponse>(
urlWithPathParams,
);

const data = this.handleResponse<ISimulationResponse>(response);
const data = this.handleResponse<ISimulationResponse<T>>(response);

return new Simulation(data);
}

public async update(simulationId: string, updateSimulation: UpdateSimulationRequestBody): Promise<Simulation> {
public async update<T extends IEventName | SimulationScenarioType>(
simulationId: string,
updateSimulation: UpdateSimulationRequestBody,
): Promise<Simulation<T>> {
const urlWithPathParams = new PathParameters(SimulationPaths.update, {
simulation_id: simulationId,
}).deriveUrl();

const response = await this.client.patch<
UpdateSimulationRequestBody,
Response<ISimulationResponse> | ErrorResponse
Response<ISimulationResponse<T>> | ErrorResponse
>(urlWithPathParams, updateSimulation);

const data = this.handleResponse<ISimulationResponse>(response);
const data = this.handleResponse<ISimulationResponse<T>>(response);

return new Simulation(data);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
*/

import type { SimulationScenarioType } from '../../../enums';
import type { IEventName } from '../../../notifications';
import type { EventMap, IEventName } from '../../../notifications';

export interface CreateSimulationRequestBody {
export interface CreateSimulationRequestBody<T extends IEventName | SimulationScenarioType> {
notificationSettingId: string;
type: IEventName | SimulationScenarioType;
type: T;
name: string;
payload?: any;
payload?: (T extends IEventName ? EventMap[T] : null) | null;
}
8 changes: 4 additions & 4 deletions src/types/simulation/simulation-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
*/

import type { SimulationScenarioType, Status } from '../../enums';
import type { IEventName } from '../../notifications';
import type { IEventName, EventMap } from '../../notifications';

export interface ISimulationResponse {
export interface ISimulationResponse<T extends IEventName | SimulationScenarioType> {
id: string;
status: Status;
notification_setting_id: string;
name: string;
type: IEventName | SimulationScenarioType;
payload?: any;
type: T;
payload?: (T extends IEventName ? EventMap[T] : null) | null;
last_run_at?: string | null;
created_at: string;
updated_at: string;
Expand Down

0 comments on commit be3ae08

Please sign in to comment.