@aire-ux/aire-condensation
Version:
Client-side serialization library for Aire-UX
217 lines (183 loc) • 5.48 kB
text/typescript
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(),
}
);