UNPKG

@aire-ux/aire-condensation

Version:

Client-side serialization library for Aire-UX

217 lines (183 loc) 5.48 kB
import TypeRegistry from "@condensation/type-registry"; import RemoteRegistry, {InvocationType} from "@condensation/remote-registry"; import {Dynamic, Address, allocate, Class, isPointer, Pointer, Region,} from "@condensation/types"; import { BooleanDeserializer, Deserializer, NumberDeserializer, StringDeserializer, TypeRegistrationDeserializer, } from "@condensation/deserializer"; export type Format = "json"; export interface Context { region: Region; create<T>(t: Class<T>, ...args: string[]): Pointer<T>; locate<T>(address: Address): T | null; move<T>(address: Address, target: Region): Pointer<T> | null; formalParams<T>( t: Class<T>, type: InvocationType, ...args: string[] ): any[]; invokeDirect<T, U>( value: T, op: string, ...args: string[] ): U | null; invoke<T, U>( address: Address | Pointer<T>, op: string, ...args: string[] ): U | null; delete<T>(address: Address): T | null; addressOf<T>(t: T): Address | null; } /** * root context for all operations */ export class Condensation { static context: Context; static registry: TypeRegistry; static remoteRegistry: RemoteRegistry; static deserializerConfigurations: Map<Class<any>, Deserializer<any>> = new Map<Class<any>, Deserializer<any>>(); static get typeRegistry(): TypeRegistry { return Condensation.registry; } static deserializerFor<T>(type: Class<T>): Deserializer<T> { const result = this.deserializerConfigurations.get(type); if (result) { return result; } const cfg = Condensation.registry.resolveConfiguration<T>(type); return new TypeRegistrationDeserializer<T>(type, cfg); } static newContext(): Context { return new DefaultCondensationContext(); } static defaultContext() { if (!Condensation.context) { Condensation.context = Condensation.newContext(); } return Condensation.context; } } class DefaultCondensationContext implements Context { constructor(readonly region = new Region("default")) { } create<T>(t: Class<T>, ...args: string[]): Pointer<T> { const actualParams = this.formalParams(t, "constructor", 'constructor', ...args); return allocate(new t(...actualParams) as T, this.region); } addressOf<T>(t: T): Address { return this.region.addressOf(t); } delete<T>(address: Address): T | null { return this.region.delete(address); } invokeDirect<T, U>(value: T, op: string, ...args: string[]): U | null { const operation = (value as any)[op] as any; if (!operation) { throw new Error(`Type ${typeof value} has no method named '${op}'`); } const formals = this.formalParams( Object.getPrototypeOf(value).constructor, "method", op, ...args ); return operation.apply(value, formals); } invoke<T, U>( address: Address | Pointer<T>, op: string, ...args: string[] ): U | null { let v: Pointer<T>; if (isPointer(address)) { v = address as Pointer<T>; } else { v = this.locate(address as Address) as Pointer<T>; } if (!v) { throw new Error( `Null pointer exception at ${address} while trying to invoke ${op}` ); } const operation = (v as any)[op] as any; if (!operation) { throw new Error(`Type ${typeof v} has no method named '${op}'`); } const formals = this.formalParams( Object.getPrototypeOf(v).constructor, "method", op, ...args ); return operation.apply(v, formals); } locate<T>(address: Address): T | null { return this.region.values[address.value]; } move<T>(address: Address, target: Region): Pointer<T> | null { const v = this.delete(address) as T; return allocate(v, target); } public formalParams<T>( t: Class<T>, type: InvocationType, operation: string, ...args: string[] ): any[] { const remotes = Condensation.remoteRegistry, remote = remotes.resolve(t), ctorArgs = remote.definitions.filter( (definition) => definition.invocationType === type && definition.invocationTarget == operation ); if (ctorArgs.length !== args.length) { throw new Error( `Error: ${type} argument count mismatch. Expected ${ctorArgs.length}, got ${args.length}` ); } ctorArgs.sort((lhs, rhs) => lhs.index - rhs.index); return ctorArgs.map((def, idx) => { const doc = args[idx], jsonValue = JSON.parse(doc); if (def.type !== Dynamic) { const deserializer = Condensation.deserializerFor(def.type); return deserializer.read(jsonValue); } else { return jsonValue; } }); } } export type RegistrationDefinition = { type: Class<any>; deserializer: Deserializer<any>; }; export function register(...registrations: RegistrationDefinition[]) { for (let reg of registrations) { Condensation.deserializerConfigurations.set(reg.type, reg.deserializer); } } export namespace Condensation { } Condensation.registry = new TypeRegistry(); Condensation.remoteRegistry = new RemoteRegistry(); register( { type: String, deserializer: new StringDeserializer(), }, { type: Boolean, deserializer: new BooleanDeserializer(), }, { type: Number, deserializer: new NumberDeserializer(), } );