UNPKG

riof

Version:

Rio framework

202 lines (166 loc) 7.04 kB
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; } } } @RioMethod() protected async init(data: Data<any, any, PublicState, PrivateState>): Promise<void> { } @RioMethod() protected async _get(data: Data<any, any, PublicState, PrivateState>): Promise<void> { data.response = {statusCode: 200} } @RioMethod() protected async destroy(data: Data<any, any, PublicState, PrivateState>): Promise<void> { } @RioMethod() 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' }); } @RioMethod() protected async getInstanceId<Input>(data: Data<Input, any, PublicState, PrivateState>): Promise<string> { return this.generateInstanceId(); } @RioMethod() 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 } } } }