-
-
Save srdjan/fe792209996031252fc713ec82d8620e to your computer and use it in GitHub Desktop.
Statically typechecked serializable protocol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { IProtocol, IRequestResponseMap, IEventMap } from "./IProtocol"; | |
import { ISerializable } from "./ISerializable"; | |
type RequestResponseMap = { | |
ping: { | |
RequestParams: {}; | |
ResponseParams: {}; | |
}; | |
echo: { | |
RequestParams: ISerializable; | |
ResponseParams: ISerializable; | |
}; | |
authenticate: { | |
RequestParams: { | |
username: string; | |
password: string; | |
}; | |
ResponseParams: { err: null; token: string } | { err: string; token: null }; | |
}; | |
}; | |
type EventMap = { | |
tick: { now: number }; | |
}; | |
type IMyProtocol = IProtocol<RequestResponseMap, EventMap>; | |
export { IMyProtocol }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ISerializable } from "./ISerializable"; | |
type IMapKey = number | symbol | string; | |
type IMessage< | |
TypeT extends "request" | "response" | "event", | |
KindT extends IMapKey, | |
ParamsT extends ISerializable | |
> = { | |
readonly type: TypeT; | |
readonly kind: KindT; | |
readonly params: ParamsT; | |
}; | |
type IRequest<KindT extends IMapKey, ParamsT extends ISerializable> = IMessage< | |
"request", | |
KindT, | |
ParamsT | |
>; | |
type IResponse<KindT extends IMapKey, ParamsT extends ISerializable> = IMessage< | |
"response", | |
KindT, | |
ParamsT | |
>; | |
type IEvent<KindT extends IMapKey, ParamsT extends ISerializable> = IMessage< | |
"event", | |
KindT, | |
ParamsT | |
>; | |
type IRequestResponseMap<KindT extends IMapKey> = { | |
readonly [kind in KindT]: { | |
readonly RequestParams: IRequest<kind, any>["params"]; | |
readonly ResponseParams: IResponse<kind, any>["params"]; | |
} | |
}; | |
type IEventMap<KindT extends IMapKey> = { | |
readonly [kind in KindT]: IEvent<kind, any>["params"] | |
}; | |
type IProtocol< | |
RequestResponsePairMapT extends IRequestResponseMap<any>, | |
EventMapT extends IEventMap<any> | |
> = { | |
readonly Request: new <KindT extends keyof RequestResponsePairMapT>( | |
kind: KindT, | |
params: RequestResponsePairMapT[KindT]["RequestParams"], | |
) => IRequest<KindT, RequestResponsePairMapT[KindT]["RequestParams"]>; | |
readonly Response: new <KindT extends keyof RequestResponsePairMapT>( | |
kind: KindT, | |
params: RequestResponsePairMapT[KindT]["ResponseParams"], | |
) => IResponse<KindT, RequestResponsePairMapT[KindT]["ResponseParams"]>; | |
readonly Event: new <KindT extends keyof EventMapT>( | |
kind: KindT, | |
params: EventMapT[KindT]["params"], | |
) => IEvent<KindT, EventMapT[KindT]["params"]>; | |
}; | |
type IServer<ProtocolT extends IProtocol<any, any>> = { | |
readonly listen: < | |
RequestT extends InstanceType<ProtocolT["Request"]>, | |
ResponseT extends InstanceType<ProtocolT["Response"]> & | |
IResponse<RequestT["kind"], any> | |
>( | |
listener: (request: RequestT) => Promise<ResponseT>, | |
) => () => void; | |
readonly emit: (event: InstanceType<ProtocolT["Event"]>) => void; | |
}; | |
type IClient<ProtocolT extends IProtocol<any, any>> = { | |
readonly subscribe: ( | |
subscriber: (event: InstanceType<ProtocolT["Event"]>) => void, | |
) => () => void; | |
readonly fetch: < | |
RequestT extends InstanceType<ProtocolT["Request"]>, | |
ResponseT extends InstanceType<ProtocolT["Response"]> & | |
IResponse<RequestT["kind"], any> | |
>( | |
request: RequestT, | |
) => Promise<ResponseT>; | |
}; | |
export { | |
IMapKey, | |
IMessage, | |
IRequest, | |
IResponse, | |
IEvent, | |
IRequestResponseMap, | |
IEventMap, | |
IProtocol, | |
IServer, | |
IClient, | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type ISerializable = | |
| string | |
| number | |
| boolean | |
| ISerializableObject | |
| ISerializableArray; | |
interface ISerializableObject { | |
readonly [key: string]: ISerializable; | |
} | |
interface ISerializableArray extends Array<ISerializable> {} | |
interface ISerializer { | |
readonly serialize: (serializable: ISerializable) => string; | |
readonly unserialize: (serialized: string) => ISerializable; | |
} | |
export { ISerializable, ISerializableObject, ISerializer }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { IKernelShellProtocol } from "./IKernelShellProtocol"; | |
import { UniversalProtocol } from "./UniversalProtocol"; | |
const MyProtocol: IKernelShellProtocol = UniversalProtocol; | |
const t1 = new MyProtocol.Request("ping", {}); // passes | |
const t2 = new MyProtocol.Request("ping", null); // fails at compile time | |
const t3 = new MyProtocol.Request("authenticate", { | |
username: "my-username", | |
}); // fails at compile time (missing property: password) | |
const t4 = new MyProtocol.Response("authenticate", { | |
err: "an error has occured", | |
token: null, | |
}); // passes | |
const t5 = new MyProtocol.Response("authenticate", { | |
err: null, | |
token: null, | |
}); // fails at compile time (err and token can't be both null) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
IEvent, | |
IMapKey, | |
IMessage, | |
IProtocol, | |
IRequest, | |
IResponse, | |
} from "./IProtocol"; | |
import { ISerializable } from "./ISerializable"; | |
class UniversalMessage< | |
TypeT extends "request" | "response" | "event", | |
KindT extends IMapKey, | |
ParamsT extends ISerializable | |
> implements IMessage<TypeT, KindT, ParamsT> { | |
readonly type: TypeT; | |
readonly kind: KindT; | |
readonly params: ParamsT; | |
constructor(type: TypeT, kind: KindT, params: ParamsT) { | |
this.type = type; | |
this.kind = kind; | |
this.params = params; | |
} | |
} | |
class UniversalRequest<KindT extends IMapKey, ParamsT extends ISerializable> | |
extends UniversalMessage<"request", KindT, ParamsT> | |
implements IRequest<KindT, ParamsT> { | |
constructor(kind: KindT, params: ParamsT) { | |
super("request", kind, params); | |
} | |
} | |
class UniversalResponse<KindT extends IMapKey, ParamsT extends ISerializable> | |
extends UniversalMessage<"response", KindT, ParamsT> | |
implements IResponse<KindT, ParamsT> { | |
constructor(kind: KindT, params: ParamsT) { | |
super("response", kind, params); | |
} | |
} | |
class UniversalEvent<KindT extends IMapKey, ParamsT extends ISerializable> | |
extends UniversalMessage<"event", KindT, ParamsT> | |
implements IEvent<KindT, ParamsT> { | |
constructor(kind: KindT, params: ParamsT) { | |
super("event", kind, params); | |
} | |
} | |
const UniversalProtocol: IProtocol<any, any> = { | |
Request: UniversalRequest, | |
Response: UniversalResponse, | |
Event: UniversalEvent, | |
}; | |
export { UniversalProtocol }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment