Notice that addTodoV2
and addTodoV3
error for extra field WRONG_FIELD
, but addTodoV1
doesn't error for extra field in line 45.

Notice that addTodoV2
and addTodoV3
error for extra field WRONG_FIELD
, but addTodoV1
doesn't error for extra field in line 45.
type TodoAction =
| { type: 'INIT' }
| { type: 'ADD_TODO', text: string }
| { type: 'REMOVE_TODO', id: string }
| { type: 'SET_COMPLETED', id: string };
type KeysOfUnion<T extends { type: any }> =
T extends { type: infer K } ? {} extends Omit<Extract<T, { type: K }>, 'type'> ? never : K : never;
function dispatch(actionType: KeysOfUnion<TodoAction>) { throw new Error('Not implemented') };
dispatch('ADD_TODO');
My aim is to create a function which has two parameters. First one is union value of type
field in Action
and second parameter should force developer to use specific object in Action
.
For example, assume that we have this function function dispatch(actionType, payload){}
dispatch('SIGN_OUT', { type: 'SIGN_OUT', userName: ''
});
or
dispatch('SIGN_IN_FAILURE', { type: 'SIGN_IN_FAILURE', error: '' });
type Action =
| { type: 'INIT' }
| { type: 'SYNC' }
| { type: 'SIGN_UP', userName: string, password: string, attributeList: Array<any> }
| { type: 'SIGN_IN', userName: string, pasword: string }
| { type: 'SIGN_IN_SUCCESS', accessToken: string }
| { type: 'SIGN_IN_FAILURE', error: string }
| { type: 'SIGN_OUT', userName: string }
| { type: 'FORGOT_PASSWORD', userName: string }
| { type: 'FORGOT_PASSWORD_SUCCESS', verificationCode: string }
| { type: 'CHANGE_PASSWORD', userName: string, oldPassword: string, newPassword: string }
| { type: 'CONFIRM_REGISTRATION', userName: string, confirmationCode: string }
| { type: 'RESEND_CONFIRMATION_CODE', userName: string }
| { type: 'DELETE_USER', userName: string }
| { type: 'GET_SESSION', userName: string }
| { type: 'GET_SESSION_SUCCESS', session: string };
type TodoAction =
| { type: 'INIT' }
| { type: 'ADD_TODO', text: string }
| { type: 'REMOVE_TODO', id: string }
| { type: 'SET_COMPLETED', id: string };
Continue reading type PropEventSource<T> = {
on<K extends string & keyof T>(eventName: `${K}Changed`, callback: (newValue: T[K]) => void): void;
};
type ReturnType<T1> = T1 & PropEventSource<T1>;
function makeWatchedObject<T1>(obj1: T1): ReturnType<T1>
function makeWatchedObject<T1, T2>(obj1: T1, obj2: T2): ReturnType<T1 & T2>
function makeWatchedObject<T1, T2, T3>(obj1: T1, obj2: T2, obj3: T3): ReturnType<T1 & T2 & T3>
function makeWatchedObject<T1, T2, T3>(obj1: T1, obj2?: T2, obj3?: T3): any {
const events = new Map<string, (a: any) => void>();
const on_: PropEventSource<T1 & T2 & T3> = {
on(eventName: string, callback: (newValue: any) => void): void {
events.set(eventName, callback);
}
}
const obj = { ...obj1, ...obj2, ...obj3, ...on_ };
const proxyObj = new Proxy(obj, {
set: function (target, property, value, receiver) {
const event = events.get(`${property as string}Changed`);
if (event) {
event(value);
return true;
}
return Reflect.set(target, property, value, receiver);
}
});
return proxyObj;
}
const personInfo = { id: 1, firstName: 'kenan', lastName: 'hancer', age: 36 };
const personAddress = { country: 'UK', city: 'London' };
const personContact = { email: 'kh@kh.com', phone: '077111-33-33' };
let person = makeWatchedObject(personInfo, personAddress, personContact);
// works! 'newName' is typed as 'string'
person.on("firstNameChanged", (newName: string) => {
console.log(`new name is ${newName.toUpperCase()}`);
});
// works! 'newAge' is typed as 'number'
person.on("ageChanged", (newAge: number) => {
if (newAge < 0) {
console.log("warning! negative age");
}
});
person.age = 34;
type MessageOf<T> = T extends { message: infer MessageType } ? MessageType : never;
const getMessage = <T extends { message: any }>(obj: T): MessageOf<T> => obj.message;
const sms = { phone: '07733838', message: ['Hello World', 'Merhaba Dunya'] };
const smsMessage = getMessage(sms); // string[]
const email = { from: 'kh@kh.com', to: 'kkkk@kkkk.com', message: 'Hello World' };
const emailMessage = getMessage(email); // string
Notice that line 3 in below code, T extends { message: unknown }
message field data type is unknown so returning value from function should be casted to any like obj.message as any
type MessageOf<T> = T extends { message: infer MessageType } ? MessageType : never;
const getMessage = <T extends { message: unknown }>(obj: T): MessageOf<T> => obj.message as any;
const sms = { phone: '07733838', message: ['Hello World', 'Merhaba Dunya'] };
const smsMessage = getMessage(sms); // string[]
const email = { from: 'kh@kh.com', to: 'kkkk@kkkk.com', message: 'Hello World' };
const emailMessage = getMessage(email); // string
I try to understand eventType
field from Event
object. But in order to use createEventGridMessage
in other TypeScript file, you need to import StartEvent
, InfoEvent
and EndEvent
types. So next example of this case looks better to me 🙂
import {v4 as uuidv4} from 'uuid';
export type BaseEvent = {
timeStamp?: number,
componentName: string,
componentVersion: string,
operationId: string,
correlationId: string,
data: any,
}
export type StartEvent = Partial<BaseEvent> & {
eventType: 'LogStart',
source: string,
trigger: string,
};
export type EndEvent = Partial<BaseEvent> & {
eventType: 'LogEnd'
destination: 'Event Grid' | 'Http Response' | 'Database' | string,
};
export type InfoEvent = Pick<EndEvent, keyof Omit<EndEvent, 'eventType'>> & { eventType: 'LogInfo' };
export type Event = StartEvent | EndEvent | InfoEvent;
export type EventGridMessageParameters<T extends Event> = Pick<EventGridMessage<T>, 'data' | 'eventType' | 'subject' | 'topic'>;
export type EventType<T extends Event> = T extends StartEvent ? 'LogStart' : T extends EndEvent ? 'LogEnd' : T extends InfoEvent ? 'LogInfo' : 'UNKNOWN EVENT TYPE';
export interface EventGridMessage<T extends Event> {
id: string;
topic: string;
subject: string;
eventType: EventType<T>;
eventTime: Date;
data: T;
dataVersion: string;
metadataVersion: string;
}
export const createEventGridMessage = <T extends Event>({
data,
eventType,
subject = "New message from unknown source",
topic = ""
}: EventGridMessageParameters<T>): EventGridMessage<T> =>
({
id: uuidv4(),
topic,
subject,
eventType,
eventTime: new Date(),
data: {...data, timeStamp: data.timeStamp || new Date().getTime()},
dataVersion: "1.0",
metadataVersion: "1.0"
});
const startLogMessage = createEventGridMessage<StartEvent>({
eventType: 'LogStart',
data: {
eventType: 'LogStart',
trigger: '',
source: '',
correlationId: '',
componentVersion: '',
componentName: '',
operationId: ''
},
subject: '',
topic: ''
});
console.log(startLogMessage);
const infoLogMessage = createEventGridMessage<InfoEvent>({
eventType: 'LogInfo',
data: {
eventType: 'LogInfo',
correlationId: '',
componentVersion: '',
componentName: '',
operationId: '',
destination: '',
},
subject: '',
topic: ''
});
console.log(infoLogMessage);
const endLogMessage = createEventGridMessage<EndEvent>({
eventType: 'LogEnd',
data: {
eventType: 'LogEnd',
timeStamp: new Date().getTime(),
correlationId: '',
componentVersion: '',
componentName: '',
operationId: '',
destination: '',
},
subject: '',
topic: ''
});
console.log(endLogMessage);
Continue reading SomeType extends OtherType ? TrueType : FalseType;
type NumberOrNot<T> = T extends number ? string : never;
type StringOrNot<T> = T extends string ? string : never;
type BooleanOrNumberOnly<T> = T extends boolean | number ? T : never;
type NewType = StringOrNumberOnly<string | number | boolean>;
const a1: NewType = true; // boolean
const a2: NewType = 23; // number
Continue reading const mockedPutObject = jest.fn();
jest.mock('@aws-sdk/client-s3', () => {
return {
S3: jest.fn(() => ({
putObject: () => mockedPutObject()
}))
}
});
jest.mock('uuid', () => ({ v4: () => '00000000-0000-0000-0000-000000000000' }));
jest.mock('uuid');
const mockedUuidv4 = uuidv4 as jest.Mock;
mockedUuidv4.mockReturnValue('00000000-0000-0000-0000-000000000000');
const mockedAjvValidate = jest.fn().mockReturnValue(true);
jest.mock('ajv', () => {
return jest.fn(() => ({
compile: () => () => mockedAjvValidate()
}))
});
jest.mock('moment', () => () => ({ format: () => '30-12-2021' }));
import { v4 as uuid } from 'uuid';
export interface IEvent {
id?: string;
eventType: string;
data: any;
}
export interface IEventRepository {
getEvent(id: string): IEvent | undefined;
setEvent(event: IEvent): IEvent;
}
const createEvent = (event: IEvent): IEvent => ({ id: uuid(), ...event });
export class EventRedisRepository implements IEventRepository {
constructor(private events: Array<IEvent> = []) { }
getEvent(id: string): IEvent | undefined {
return this.events.find(item => item.id === id);
}
setEvent(event: IEvent): IEvent {
const newEvent: IEvent = createEvent(event);
this.events.push(newEvent);
return newEvent;
}
}
export class EventCosmosDbRepository implements IEventRepository {
constructor(private events: Array<IEvent> = []) { }
getEvent(id: string): IEvent | undefined {
return this.events.find(item => item.id === id);
}
setEvent(event: IEvent): IEvent {
const newEvent: IEvent = createEvent(event);
this.events.push(newEvent);
return newEvent;
}
}
import { Persister } from '../src';
import { EventRedisRepository, EventCosmosDbRepository, IEventRepository, IEvent } from './sample';
const mockedRedisGetEvent = jest.fn();
const mockedRedisSetEvent = jest.fn();
const mockedCosmosdbGetEvent = jest.fn();
const mockedCosmosdbSetEvent = jest.fn();
jest.mock('./sample', () => {
return {
EventRedisRepository: jest.fn(() => ({
getEvent: () => mockedRedisGetEvent(),
setEvent: () => mockedRedisSetEvent(),
})),
EventCosmosDbRepository: jest.fn(() => ({
getEvent: () => mockedCosmosdbGetEvent(),
setEvent: () => mockedCosmosdbSetEvent(),
})),
}
});
const eventRedisRepository = new EventRedisRepository();
const eventCosmosdbRepository = new EventCosmosDbRepository();
const module_name = `${Persister.name}`;
describe(`${module_name} Test`, () => {
it('should call setEvent function in both persisters', () => {
const eventPersister = new Persister<IEventRepository>([eventRedisRepository, eventCosmosdbRepository]);
const eventId = '4e0c71bb-eedf-43dd-b9a1-911a7ed6d74f';
const newEvent: IEvent = { id: eventId, eventType: 'Submission', data: {} };
eventPersister.set('setEvent', op => op(newEvent));
expect(mockedRedisSetEvent).toHaveBeenCalledTimes(1);
expect(mockedCosmosdbSetEvent).toHaveBeenCalledTimes(1);
});
});
import { Persister } from '../src';
import { EventRedisRepository, EventCosmosDbRepository, IEventRepository, IEvent } from './sample';
import { mocked } from 'jest-mock'
jest.mock('./sample');
const eventRedisRepository = new EventRedisRepository();
const eventCosmosdbRepository = new EventCosmosDbRepository();
const mockedEventRedisRepository = mocked(eventRedisRepository, true);
const mockedEventCosmosdbRepository = mocked(eventRedisRepository, true);
const module_name = `${Persister.name}`;
describe(`${module_name} Test`, () => {
it('should call setEvent function in both persisters', () => {
const eventPersister = new Persister<IEventRepository>([eventRedisRepository, eventCosmosdbRepository]);
const eventId = '4e0c71bb-eedf-43dd-b9a1-911a7ed6d74f';
const newEvent: IEvent = { id: eventId, eventType: 'Submission', data: {} };
eventPersister.set('setEvent', op => op(newEvent));
expect(mockedEventRedisRepository.setEvent).toHaveBeenCalledTimes(1);
expect(mockedEventCosmosdbRepository.setEvent).toHaveBeenCalledTimes(1);
});
});