riof
Version:
Rio framework
202 lines (166 loc) • 7.04 kB
text/typescript
import RDK, {Data, Response, RioEvent, Schedule, State, Task} from "@retter/rdk";
import z, {ZodObject, ZodRawShape, ZodType} from "zod";
import {KeyValueString} from "@retter/rdk/src";
interface RioMethodProps {
type?: "READ" | "STATIC" | "WRITE" | "QUEUED_WRITE" // Type of the method. Default is WRITE.
queryStringModel?: RioValidationModel // Name of the validation model for query strings
inputModel?: RioValidationModel // Name of the validation model for input body
outputModel?: RioValidationModel // Name of the validation model for output body
errorModel?: RioValidationModel // Name of the validation model for output body
schedule?: string // Schedule rule. It's only available for STATIC methods.
}
export function RioMethod(options?: RioMethodProps) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
let method = descriptor.value!;
descriptor.value = function () {
if(options?.inputModel){
options?.inputModel?.parse(arguments[0].body);
}
return method.apply(this, arguments);
};
};
}
interface RioClassProps {
architecture?: 'arm64' | 'x86_64' // Architecture for methods. Default value is arm64 which is faster.
accelerated?: boolean // Flag to decide whether to cache instances or not
description?: string // Description to put into the auto-generated documentation.
privateStateModel?: RioValidationModel // Name of the validation model for query strings
publicStateModel?: RioValidationModel // Name of the validation model for input body
}
export function RioClass(options?: RioClassProps) {
return function (target: Function) {
};
}
export enum RioClassReservedMethods {
INIT = 'INIT',
STATE = 'STATE',
GET = 'GET',
DESTROY = 'DESTROY',
}
export enum RioReservedUserIdentities {
DEVELOPER = 'developer',
ENDUSER = 'enduser',
NONE = 'none',
ANONYMOUSUSER = 'anonymous_user',
}
type ClassLookup = { name: string, value: string };
export type RioClassConstructor = string | undefined | { lookup: ClassLookup } | { body: any };
export type RioValidationModel<T extends ZodRawShape = any> = ZodObject<T>;
export type RioMethodModelType<T extends ZodType<any, any, any>> = z.infer<T>;
export class Rio<PrivateState = any, PublicState = any> {
protected readonly body?: RioClassConstructor;
protected readonly lookup?: ClassLookup;
protected instanceId?: string;
protected newInstance: boolean = true;
protected state: State<PublicState, PrivateState, any, any> = {};
protected schedule: Schedule[] = [];
protected tasks: Task[] = [];
protected events: RioEvent[] = [];
constructor(constructorData?: RioClassConstructor) {
if (constructorData && typeof constructorData === 'string') {
this.instanceId = constructorData;
this.newInstance = false;
} else if (constructorData && typeof constructorData === 'object') {
if ((constructorData as { lookup: ClassLookup }).lookup) {
this.lookup = (constructorData as { lookup: ClassLookup }).lookup;
this.newInstance = false;
} else if((constructorData as { body: any }).body) {
this.body = constructorData;
}
}
}
protected async init(data: Data<any, any, PublicState, PrivateState>): Promise<void> {
}
protected async _get(data: Data<any, any, PublicState, PrivateState>): Promise<void> {
data.response = {statusCode: 200}
}
protected async destroy(data: Data<any, any, PublicState, PrivateState>): Promise<void> {
}
protected async authorizer(data: Data<any, any, PublicState, PrivateState>): Promise<boolean> {
if (RioReservedUserIdentities.DEVELOPER) return true;
switch (data.context.methodName) {
case RioClassReservedMethods.INIT:
case RioClassReservedMethods.GET:
case RioClassReservedMethods.DESTROY:
case RioClassReservedMethods.STATE:
if (RioReservedUserIdentities.ENDUSER) return true;
break;
default:
break;
}
throw new RioMethodError({
code: 'PERMISSION_DENIED',
statusCode: 403,
message: 'Permission denied',
title: 'Permission Denied'
});
}
protected async getInstanceId<Input>(data: Data<Input, any, PublicState, PrivateState>): Promise<string> {
return this.generateInstanceId();
}
protected async getState<Input>(data: Data<Input, any, PublicState, PrivateState>): Promise<State<any, any, any, any>> {
return data.state;
}
protected generateInstanceId(): string {
return BigInt(process.hrtime.bigint()).toString(32);
}
/** This method is magical and should not be used directly. **/
private setDataProperties(data: Data<any, any, PublicState, PrivateState>) {
this.state = data.state
}
/** This method is magical and should not be used directly. **/
private async getRioClassInstance(): Promise<this> {
if(this.instanceId || this.lookup) return this;
const rdk = new RDK();
const response = await rdk.getInstance({
classId: this.constructor.name,
body: this.body,
instanceId: this.instanceId,
lookupKey: this.lookup
})
if (!response || response.statusCode >= 400 && !response.body && !response.body.instanceId) {
throw new Error('Failed to get instance');
}
this.instanceId = response.body.instanceId;
this.newInstance = !!(response.body.newInstance as (undefined | boolean));
return this
}
}
export interface RioMethodRequest<T = any> {
httpMethod?: string
body?: T
headers?: KeyValueString
queryStringParams?: Record<string, any>
}
export class RioMethodError extends Error {
readonly message: string = 'An error occurred!';
readonly title: string = 'Error';
private readonly statusCode: number = 400;
private readonly code?: string;
private readonly addons?: any;
constructor(props?: { statusCode?: number, code?: string, message?: string, title?: string, addons?: any }) {
super();
if (props?.statusCode) this.statusCode = props.statusCode;
if (props?.code) this.code = props.code;
if (props?.message) this.message = props.message;
if (props?.title) this.title = props.title;
if (props?.addons) this.addons = props.addons;
}
getRioResponse(): Response {
return {
statusCode: this.statusCode,
body: {
title: this.title,
message: this.message,
code: this.code,
addons: this.addons
}
}
}
}